<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
    <title data-i18n="pageTitle">VDO.Ninja OBS Control Dock</title>
    <style>
        body {
            font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            margin: 0;
            padding: 15px;
            background-color: #181a1b;
            color: #e0e0e0;
            font-size: 14px;
            line-height: 1.6;
        }
        .language-switcher {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .language-switcher label {
            margin-right: 0;
            color: #c0c0c0;
            font-size: 0.9em;
            margin-bottom: 0;
        }
        .language-switcher select {
            padding: 5px 8px;
            border-radius: 4px;
            background-color: #25272c;
            color: #e0e0e0;
            border: 1px solid #4a4d54;
            font-size: 0.9em;
            margin-bottom: 0;
            width: auto;
            min-width: 120px;
        }
        h1 {
            color: #d0d0d0;
            margin: 20px 0 20px 0;
            padding-bottom: 15px;
            font-size: 1.8em;
            font-weight: 600;
            border-bottom: 1px solid #3e4147;
        }
        .container {
            margin-bottom: 15px;
            padding: 15px;
            background-color: #2c2e33;
            border-radius: 8px;
            border: 1px solid #3e4147;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        }
        .collapsible {
            cursor: pointer;
            user-select: none;
            padding: 12px 15px;
            position: relative;
            font-weight: 600;
            font-size: 1.1em;
            background: #35383d;
            margin: -15px -15px 10px -15px;
            padding-left: 15px;
            border-bottom: 1px solid #3e4147;
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
            transition: background-color 0.2s ease;
        }
        .collapsible.collapsed {
            border-bottom-left-radius: 8px;
            border-bottom-right-radius: 8px;
            margin-bottom: 0;
            border-bottom: none;
        }
        .container .collapsible:last-child.collapsed {
            margin-bottom: -15px;
        }
        .collapsible[data-state="expand"]::after {
            content: "▼";
        }
        .collapsible[data-state="collapse"]::after {
            content: "▲";
        }
        .collapsible::after {
            position: absolute;
            right: 15px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 14px;
            color: #a0a0b0;
            transition: transform 0.2s ease;
        }
        .collapsible[data-state="expand"]::before {
        }
        .collapsible[data-state="collapse"]::before {
        }
        .collapsible::before {
            content: var(--before-text, "Click");
            position: absolute;
            right: 40px;
            font-size: 10px;
            color: #666;
            font-weight: normal;
        }
        .collapsible:hover {
            background: #404348;
        }
        .collapsible-content {
            max-height: 1000px;
            overflow: hidden;
            transition: max-height 0.3s ease-in-out, padding-top 0.3s ease-in-out,
                opacity 0.3s ease-in-out;
            padding-top: 10px;
            opacity: 1;
        }
        .collapsible-content.collapsed {
            max-height: 0;
            padding-top: 0;
            opacity: 0;
            overflow: hidden;
        }
        .container:nth-of-type(6) .collapsible-content:not(.collapsed) {
            max-height: none;
        }
        label {
            display: block;
            margin-bottom: 6px;
            font-weight: 500;
            color: #c0c0c0;
        }
        input[type="text"],
        input[type="password"],
        input[type="number"],
        select,
        textarea {
            width: calc(
                100% - 22px
            );
            padding: 10px;
            margin-bottom: 12px;
            border: 1px solid #4a4d54;
            border-radius: 6px;
            background-color: #25272c;
            color: #e0e0e0;
            font-size: 1em;
            box-sizing: border-box;
            transition: border-color 0.2s ease, box-shadow 0.2s ease;
        }
        textarea {
            min-height: 80px;
            resize: vertical;
        }
        input[type="text"]:focus,
        input[type="password"]:focus,
        input[type="number"]:focus,
        select:focus,
        textarea:focus {
            border-color: #007aff;
            box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.25);
            outline: none;
        }
        select {
            padding-right: 30px;
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23A0A0B0'%3E%3Cpath d='M8 11L3 6h10L8 11z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 10px center;
            background-size: 12px;
        }
        select[multiple] {
            background-image: none;
            min-height: 100px;
            padding-right: 10px;
        }
        button {
            padding: 10px 15px;
            background-color: #4a5060;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            margin-right: 5px;
            margin-bottom: 5px;
            font-size: 0.95em;
            font-weight: 500;
            transition: background-color 0.2s ease, transform 0.1s ease;
        }
        button:hover {
            background-color: #5a6070;
        }
        button:active {
            transform: translateY(1px);
        }
        button.connected {
            background-color: #007aff;
        }
        button.connected:hover {
            background-color: #0056b3;
        }
        button.disconnected {
            background-color: #6c757d;
        }
        button.disconnected:hover {
            background-color: #5a6268;
        }
        .blur-field {
            filter: blur(5px);
            transition: filter 0.2s ease;
        }
        .blur-field:focus {
            filter: blur(0);
        }
        #vdoNinjaIframe {
            width: 1px;
            height: 1px;
            position: absolute;
            left: -1000px;
            top: -1000px;
            border: 0;
        }
        .log-area {
            height: 120px;
            background-color: #202225;
            color: #b0b0b0;
            border: 1px solid #4a4d54;
            border-radius: 6px;
            overflow-y: scroll;
            padding: 10px;
            font-family: "Consolas", "Monaco", "Lucida Console", monospace;
            font-size: 0.9em;
            margin-top: 10px;
            white-space: pre-wrap;
        }
        .status-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-left: 8px;
            background-color: #555;
            transition: background-color 0.3s ease, box-shadow 0.3s ease;
        }
        .status-indicator.connected {
            background-color: #007aff;
            box-shadow: 0 0 8px rgba(0, 122, 255, 0.7);
        }
        .status-indicator.error {
            background-color: #dc3545;
            box-shadow: 0 0 8px rgba(220, 53, 69, 0.7);
        }
        .stream-list, .layout-config-list {
            background-color: #202225;
            border: 1px solid #4a4d54;
            border-radius: 6px;
            padding: 5px;
            margin-top: 5px;
            transition: max-height 0.3s ease;
        }
        .stream-list:empty {
            min-height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #888;
            padding: 10px;
        }
        .layout-config-list.is-empty {
            min-height: 0;
            padding: 0;
            border: none;
            background-color: transparent;
            margin: 0;
        }
        .stream-item, .layout-config-item {
            padding: 10px 12px;
            border-bottom: 1px solid #383b40;
            font-size: 1em;
            word-break: break-word;
            background-color: #2c2e33;
            margin: 0 0 8px 0;
            border-radius: 4px;
        }
        .stream-item:last-child, .layout-config-item:last-child {
            border-bottom: none;
            margin-bottom: 0;
        }
        h2 {
            color: #c8c8c8;
            margin: 5px 0;
            font-size: 1.1em;
        }
        h3 {
            color: #b8b8b8;
            font-size: 1.0em;
            margin-top: 10px;
            margin-bottom: 8px;
            padding-bottom: 5px;
            border-bottom: 1px dashed #4a4d54;
        }
        small {
            color: #9090a0;
            font-size: 0.88em;
            display: block;
            margin-top: 3px;
            line-height: 1.4;
        }
        .add-stream-btn {
            background-color: #28a745 !important;
        }
        .add-stream-btn:hover {
            background-color: #218838 !important;
        }
        button.add-stream-btn[style*="rgb(244, 67, 54)"],
        button.add-stream-btn[style*="#F44336"] {
            background-color: #dc3545 !important;
        }
        button.add-stream-btn[style*="rgb(244, 67, 54)"]:hover,
        button.add-stream-btn[style*="#F44336"]:hover {
            background-color: #c82333 !important;
        }
        .highlight-btn {
            background-color: #17a2b8 !important;
        }
        .highlight-btn:hover {
            background-color: #138496 !important;
        }
        button.highlight-btn[style*="rgb(244, 67, 54)"],
        button.highlight-btn[style*="#F44336"] {
            background-color: #ffc107 !important;
            color: #212529 !important;
        }
        button.highlight-btn[style*="rgb(244, 67, 54)"]:hover,
        button.highlight-btn[style*="#F44336"]:hover {
            background-color: #e0a800 !important;
        }
        .screen-share-btn {
            background-color: #6f42c1 !important;
        }
        .screen-share-btn:hover {
            background-color: #5a2aa8 !important;
        }
        button.screen-share-btn[style*="rgb(244, 67, 54)"],
        button.screen-share-btn[style*="#F44336"] {
            background-color: #fd7e14 !important;
        }
        button.screen-share-btn[style*="rgb(244, 67, 54)"]:hover,
        button.screen-share-btn[style*="#F44336"]:hover {
            background-color: #e66c00 !important;
        }
        .status-line {
            font-size: 1em;
            margin-top: 10px;
            display: flex;
            align-items: center;
        }
        #obsConnectionStatus,
        #vdoNinjaConnectionStatus {
            margin-left: 8px;
            color: #b0b0b0;
        }
        input[type="checkbox"] {
            accent-color: #007aff;
            margin-right: 8px;
            width: 15px;
            height: 15px;
            vertical-align: middle;
            flex-shrink: 0;
        }
        .checkbox-label {
            display: flex;
            align-items: center;
            margin-bottom: 5px;
            color: #c0c0c0;
            font-size: 0.95em;
        }
        .checkbox-label input {
            margin-bottom: 0;
            margin-right: 8px;
        }
        ::-webkit-scrollbar {
            width: 10px;
            height: 10px;
        }
        ::-webkit-scrollbar-track {
            background: #202225;
            border-radius: 5px;
        }
        ::-webkit-scrollbar-thumb {
            background: #4a4d54;
            border-radius: 5px;
            border: 2px solid #202225;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: #007aff;
        }
        * {
            scrollbar-width: auto;
            scrollbar-color: #4a4d54 #202225;
        }
        .flex-row {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
            align-items: center;
        }
        #obsSceneNameInput {
            display: none !important;
        }
        .prefix-input-label {
            margin-bottom: 4px;
            display: block;
            font-size: 0.9em;
            color: #a0a0b0;
        }
        .prefix-group {
            margin-bottom: 10px;
        }
        a[href^="https://docs.vdo.ninja/advanced-settings/video-parameters/codec"] {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            text-decoration: none;
            color: #a0a0b0;
            font-size: 12px;
            border: 1px solid #a0a0b0;
            border-radius: 50%;
            width: 18px;
            height: 18px;
            font-weight: bold;
            vertical-align: middle;
            margin-left: 4px;
            margin-bottom: 0;
            transition: background-color 0.2s ease, color 0.2s ease,
                border-color 0.2s ease;
        }
        a[href^="https://docs.vdo.ninja/advanced-settings/video-parameters/codec"]:hover {
            background-color: #007aff;
            color: white;
            border-color: #007aff;
        }
        .stream-mapping, .layout-config {
            background-color: #25272c;
            padding: 12px;
            border: 1px solid #383b40;
            border-radius: 6px;
            margin-bottom: 10px;
        }
        .stream-mapping .flex-row, .layout-config .flex-row {
            margin-bottom: 8px;
        }
        .stream-mapping .flex-row:last-child, .layout-config .flex-row:last-child {
            margin-bottom: 0;
        }
        .stream-mapping input[type="text"],
        .stream-mapping select,
        .layout-config select {
            margin-bottom: 0;
            font-size: 0.9em;
            padding: 8px;
        }
        .stream-mapping .remove-mapping-btn, .layout-config .remove-layout-btn {
            padding: 6px 8px;
            background-color: #c82333 !important;
            font-size: 0.9em;
            margin-left: auto;
        }
        .stream-mapping .remove-mapping-btn:hover, .layout-config .remove-layout-btn:hover {
            background-color: #a01c28 !important;
        }
        .stream-mapping small, .layout-config small {
            font-size: 0.8em;
            margin-top: 5px;
        }
        .mapping-stream-id, .layout-config-scene-select {
            flex-basis: 100px;
            flex-grow: 1;
            min-width: 80px;
        }
        .mapping-label, .layout-config-type-select {
            flex-basis: 140px;
            flex-grow: 2;
            min-width: 100px;
        }
        .mapping-match-type {
            flex-basis: 100px;
            flex-grow: 1;
            min-width: 90px;
        }
        .mapping-scene-name {
            flex-basis: 150px;
            flex-grow: 2;
            min-width: 120px;
        }
        .remove-mapping-btn, .remove-layout-btn {
            flex-shrink: 0;
        }
        #addStreamMappingBtn, #addLayoutConfigBtn {
            background-color: #0069d9;
            border-color: #0062cc;
        }
        #addStreamMappingBtn:hover, #addLayoutConfigBtn:hover {
            background-color: #005cbf;
            border-color: #0056b3;
        }
        .stream-mapping .checkbox-label, .layout-config .checkbox-label {
            font-size: 0.9em;
            margin-bottom: 0;
        }
        .settings-group {
            margin-top: 15px;
            border-top: 1px solid #3e4147;
            padding-top: 15px;
        }
        .settings-group:first-child {
            margin-top: 0;
            border-top: none;
            padding-top: 0;
        }
        .layout-config-item .flex-row {
            align-items: center;
        }
        .layout-config-item .flex-row label {
            margin-bottom: 0;
            margin-right: 5px;
        }
        .layout-config-item .flex-row select {
            flex-grow: 1;
            width: auto;
        }
        .layout-config-item .remove-layout-btn {
            margin-left: 10px;
        }
        .layout-specific-controls {
            padding: 10px;
            margin-top: 10px;
            background-color: #25272c;
            border-radius: 4px;
            border: 1px solid #383b40;
        }
        .layout-specific-controls label {
            font-size: 0.9em;
            color: #b0b0b0;
            margin-bottom: 4px;
        }
        .layout-specific-controls input[type="range"] {
            width: calc(100% - 100px);
            margin-bottom: 0px;
        }
        .layout-specific-controls input[type="number"] {
            width: 60px;
            padding: 6px;
            font-size: 0.9em;
            margin-bottom: 0px;
        }
        .layout-specific-controls .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 8px;
        }
        .layout-specific-controls .slider-container input[type="range"] {
            flex-grow: 1;
            margin-bottom: 0;
        }
        .layout-specific-controls .slider-container .slider-label {
            min-width: 80px;
            flex-shrink: 0;
        }
        .layout-specific-controls .slider-container span {
            min-width: 35px;
            text-align: right;
            font-size: 0.9em;
            color: #c0c0c0;
            display: none;
        }
        .layout-specific-controls .checkbox-label {
            font-size: 0.9em;
            margin-top: 8px;
        }
        #sourceCreationScenesContainer {
            display: flex;
            flex-direction: column;
            width: 300px;
            flex-grow: 1;
            max-height: 150px;
            overflow-y: auto;
            border: 1px solid #4a4d54;
            border-radius: 6px;
            padding: 10px;
            background-color: #25272c;
        }
        .checkbox-item-row {
            display: flex;
            align-items: center;
            margin-bottom: 8px;
        }
        .checkbox-item-row:last-child {
            margin-bottom: 0;
        }
        .checkbox-item-row input[type="checkbox"] {
            margin-right: 8px;
        }
        .checkbox-item-row label {
            margin-bottom: 0;
            font-weight: normal;
            color: #e0e0e0;
            cursor: pointer;
            flex-grow: 1;
        }
        .container:nth-of-type(7) .collapsible-content {
            padding-top: 2px !important;
        }
        .stream-item {
            padding: 4px 6px !important;
        }
        .stream-item div[style*="font-weight: 600"] {
            font-size: 0.7em !important;
            margin-bottom: 1px !important;
        }
        .stream-item small {
            line-height: 1.1 !important;
        }
        .stream-item small[style*="display: block"] {
            display: inline !important;
        }
        .stream-item div[style*="margin-top: 2px"] {
            display: inline !important;
            margin-top: 0 !important;
            margin-left: 8px;
        }
        .stream-item div[style*="margin-top: 2px"]::before {
            content: "|";
            margin-right: 8px;
            color: #6c757d;
        }
        .stream-item .flex-row {
            margin-top: 4px !important;
            gap: 5px !important;
        }
        .stream-item .add-stream-btn,
        .stream-item .highlight-btn,
        .stream-item .screen-share-btn {
            padding: 4px 8px !important;
            font-size: 0.85em !important;
        }
        .layout-config-item .remove-layout-btn {
            padding: 6px 8px !important;
            font-size: 0.9em !important;
            background-color: #c82333 !important;
        }
        .layout-config-item .remove-layout-btn:hover {
            background-color: #a01c28 !important;
        }
        .layout-config-item > .flex-row {
            align-items: center !important;
        }
    </style>
    <style id="customUserCss"></style>
  </head>
  <body>
    <h1 data-i18n="mainHeading">VDO.Ninja OBS Control</h1>
    <div class="container">
      <div class="language-switcher">
        <label for="languageSelector" data-i18n="languageSwitcher.label">Language:</label>
        <select id="languageSelector">
          <option value="en">English</option>
          <option value="es">Español</option>
          <option value="fr">Français</option>
          <option value="de">Deutsch</option>
          <option value="it">Italiano</option>
          <option value="pt">Português</option>
          <option value="ru">Русский</option>
          <option value="ja">日本語</option>
          <option value="ko">한국어</option>
          <option value="zh-CN">简体中文</option>
          <option value="nl">Nederlands</option>
          <option value="pl">Polski</option>
        </select>
      </div>
    </div>
    <div class="container">
      <h2 class="collapsible" data-state="expand" data-i18n="obsConnection.title">OBS WebSocket Connection</h2>
      <div class="collapsible-content">
        <label for="obsWsUrl" data-i18n="obsConnection.websocketUrlLabel">WebSocket URL:</label>
        <input type="text" id="obsWsUrl" value="ws://localhost:4455" />
        <label for="obsWsPassword" data-i18n="obsConnection.passwordLabel">Password:</label>
        <input type="password" id="obsWsPassword" value="" />
        <div class="prefix-group">
          <label
            for="cameraPrefix"
            id="cameraSubPrefixLabelText"
            class="prefix-input-label"
            data-i18n="obsConnection.cameraPrefixLabel"
            >General Camera prefix:</label
          >
          <input type="text" id="cameraPrefix" value="VDO" />
        </div>
        <div class="prefix-group">
          <label
            for="reactionSubPrefix"
            id="reactionSubPrefixLabelText"
            class="prefix-input-label"
            data-i18n="obsConnection.reactionPrefixLabel"
            >Reaction prefix: VDO.</label
          >
          <input type="text" id="reactionSubPrefix" value="Screen" />
        </div>
        <div class="prefix-group">
          <label
            for="highlightSubPrefix"
            id="highlightSubPrefixLabelText"
            class="prefix-input-label"
            data-i18n="obsConnection.highlightPrefixLabel"
            >Highlight prefix: VDO.</label
          >
          <input type="text" id="highlightSubPrefix" value="Highlight" />
        </div>
        <div class="status-line">
          <button id="obsConnectBtn" data-i18n="obsConnection.connectButton">Connect</button>
          <span id="obsConnectionStatus" data-i18n="obsConnection.statusDisconnected">Status: Disconnected</span>
          <span id="obsStatusIndicator" class="status-indicator"></span>
        </div>
      </div>
    </div>
    <div class="container">
      <h2 class="collapsible" data-state="expand" data-i18n="vdoNinjaSettings.title">VDO.Ninja Settings</h2>
      <div class="collapsible-content">
        <label for="vdoNinjaBaseUrl" data-i18n="vdoNinjaSettings.baseUrlLabel">VDO.Ninja Base URL:</label>
        <input
          type="text"
          id="vdoNinjaBaseUrl"
          data-i18n="[placeholder]vdoNinjaSettings.baseUrlPlaceholder"
          placeholder="https://vdo.ninja"
          value="https://vdo.ninja"
        />
        <label for="vdoNinjaRoom" data-i18n="vdoNinjaSettings.roomNameLabel">Room Name:</label>
        <input
          type="text"
          id="vdoNinjaRoom"
          data-i18n="[placeholder]vdoNinjaSettings.roomNamePlaceholder"
          placeholder="e.g., MyNinjaRoom"
          class="blur-field"
        />
        <label for="vdoNinjaPassword" data-i18n="vdoNinjaSettings.passwordLabel">Password:</label>
        <input
          type="password"
          id="vdoNinjaPassword"
          data-i18n="[placeholder]vdoNinjaSettings.passwordPlaceholder"
          placeholder="Room or &password"
        />
        <label for="vdoNinjaStreamIds" data-i18n="vdoNinjaSettings.streamIdsLabel">Stream IDs:</label>
        <input
          type="text"
          id="vdoNinjaStreamIds"
          data-i18n="[placeholder]vdoNinjaSettings.streamIdsPlaceholder"
          placeholder="streamId1,streamId2"
          class="blur-field"
        />
        <small data-i18n="vdoNinjaSettings.roomOrStreamIdsNeeded">Room Name or Stream ID(s) needed</small>
        </div>
    </div>
    <div class="container">
      <h2 class="collapsible" data-state="collapse" data-i18n="streamIdMappings.title">Stream ID Mappings</h2>
      <div class="collapsible-content collapsed">
        <div id="streamMappingContainer">
          <div id="streamMappings"></div>
          <button id="addStreamMappingBtn" style="margin-top: 10px" data-i18n="streamIdMappings.addNewMappingButton">
            Add New Mapping
          </button>
        </div>
      </div>
    </div>
    <div class="container">
      <h2 class="collapsible" data-state="expand" data-i18n="obsTargetSettings.title">OBS Target Settings</h2>
      <div class="collapsible-content">
        <div class="settings-group">
            <label data-i18n="obsTargetSettings.sourceCreationScenesLabel">Cenas para Criação de Fontes:</label>
            <small data-i18n="obsTargetSettings.sourceCreationScenesDesc">Selecione uma ou mais cenas. A primeira marcada será a principal, as outras para cópia.</small>
            <div class="flex-row" style="gap: 10px; margin-top: 5px; align-items: flex-start;">
                <div id="sourceCreationScenesContainer">
                    <span data-i18n="obsTargetSettings.loadingScenes">Carregando cenas...</span>
                </div>
                <button id="loadSourceCreationScenesBtn" data-i18n="obsTargetSettings.refetchScenesButton">Re-Fetch Scenes</button>
            </div>
        </div>
        <input type="text" id="obsSceneNameInput" style="display: none" />
        <div class="settings-group">
          <label style="font-weight: 600; font-size: 1.05em" data-i18n="obsTargetSettings.screenShareSettings.title">Screen Sharing Settings:</label>
          <div class="flex-row" style="gap: 15px; align-items: flex-start">
            <div class="flex-row" style="gap: 8px">
              <div>
                <label for="screenShareWidth" style="font-size: 0.95em" data-i18n="obsTargetSettings.screenShareSettings.widthLabel">Width:</label>
                <input
                  type="number"
                  id="screenShareWidth"
                  value="1920"
                  style="width: 100px"
                />
              </div>
              <div>
                <label for="screenShareHeight" style="font-size: 0.95em" data-i18n="obsTargetSettings.screenShareSettings.heightLabel">Height:</label>
                <input
                  type="number"
                  id="screenShareHeight"
                  value="1080"
                  style="width: 100px"
                />
              </div>
            </div>
          </div>
           <small data-i18n="obsTargetSettings.screenShareSettings.resolutionNote">This resolution will be used for screen sharing sources in scenes with a 'Reaction' layout.</small>
        </div>
        <div
          id="autoSourceOptions"
          class="settings-group flex-row"
          style="gap: 15px"
        >
          <label class="checkbox-label">
            <input type="checkbox" id="autoAddSources" checked />
            <span data-i18n="obsTargetSettings.autoAddSourcesLabel">Auto-add new streams as sources</span>
          </label>
          <label class="checkbox-label">
            <input type="checkbox" id="autoRemoveSources" checked />
            <span data-i18n="obsTargetSettings.autoRemoveSourcesLabel">Auto-remove sources on disconnect</span>
          </label>
        </div>
        <div class="settings-group">
          <div class="flex-row" style="align-items: flex-start; gap: 15px">
            <div>
              <label for="sourceSizing" data-i18n="obsTargetSettings.newSourceSizing.label">Default Source Sizing:</label>
              <select id="sourceSizing" style="width: 250px">
                <option value="defaultSize" data-i18n="obsTargetSettings.newSourceSizing.defaultSizeOption">Default (1920x1080 at 0,0)</option>
                <option value="bestFit" data-i18n="obsTargetSettings.newSourceSizing.bestFitOption">Best Fit (Preserve Aspect)</option>
                <option value="stretchToFill" data-i18n="obsTargetSettings.newSourceSizing.stretchToFillOption">Stretch to Fill Screen</option>
                </select>
            </div>
            <div>
              <label for="sourceCodec" data-i18n="obsTargetSettings.codec.label">Codec:</label>
              <div class="flex-row" style="gap: 2px">
                <select id="sourceCodec" style="width: 150px">
                  <option value="" data-i18n="obsTargetSettings.codec.noneOption">none</option>
                  <option value="h264">h264</option>
                  <option value="vp8">vp8</option>
                  <option value="vp9">vp9</option>
                  <option value="av1">av1</option>
                  <option value="h265">h265</option>
                  <option value="webp">webp</option>
                  <option value="hardware">hardware</option>
                </select>
                <a
                  href="https://docs.vdo.ninja/advanced-settings/video-parameters/codec"
                  target="_blank"
                  data-i18n="[title]obsTargetSettings.codec.learnMoreTitle"
                  title="Learn more about codec options"
                  >?</a
                >
              </div>
            </div>
          </div>
          <small data-i18n="obsTargetSettings.newSourceSizing.overrideNote">Specific layouts defined in the "Layouts" section will override these for those scenes.</small>
        </div>
      </div>
    </div>
    <div class="container"> <h2 class="collapsible" data-state="collapse" data-i18n="sceneLayouts.title">Scene Layouts</h2>
      <div class="collapsible-content collapsed">
        <div id="layoutConfigContainer">
          <div id="layoutConfigsList" class="layout-config-list">
             </div>
          <button id="addLayoutConfigBtn" style="margin-top: 10px;" data-i18n="sceneLayouts.addNewLayoutButton">
            Add New Layout
          </button>
          <small data-i18n="sceneLayouts.description">Define automatic layouts for specific OBS scenes. These will override the default source sizing for the configured scenes.</small>
        </div>
      </div>
    </div>
    <div class="container">
        <h2 class="collapsible" data-state="expand" data-i18n="activeStreams.title">Active Streams</h2>
        <div class="collapsible-content">
        <div id="streamList" class="stream-list">
          <div
            class="stream-item"
            style="
              text-align: center;
              background-color: transparent;
              border: none;
              color: #888;
            "
            data-i18n="activeStreams.noActiveStreams"
          >
            No active streams
          </div>
        </div>
      </div>
    </div>
    <div class="container">
        <h2 class="collapsible" data-state="collapse" data-i18n="customCss.title">Custom CSS</h2>
        <div class="collapsible-content collapsed">
            <label for="customCssInput" data-i18n="customCss.label">Enter your custom CSS here:</label>
            <textarea id="customCssInput" rows="10"></textarea>
            <small data-i18n="customCss.description">This CSS will be applied to the sources.</small>
        </div>
    </div>
    <iframe
      id="vdoNinjaIframe"
      allow="encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;midi *;geolocation;camera *;microphone *;fullscreen;picture-in-picture;display-capture;accelerometer;autoplay;gyroscope;screen-wake-lock;"
    ></iframe>
    <div class="container">
      <h2 class="collapsible" data-state="expand" data-i18n="log.title">Log</h2>
      <div class="collapsible-content">
        <div id="logArea" class="log-area"></div>
      </div>
    </div>
    <script>
      
      let logEntries = [];
      const languageSelector = document.getElementById('languageSelector');
      
      
      const DEFAULT_GRID_MARGIN = 75;
      const DEFAULT_GRID_SPACING = 30;
      const DEFAULT_GRID_X_OFFSET = 0;
      const DEFAULT_GRID_Y_OFFSET = 0;
      const DEFAULT_GRID_SPLIT_SCREEN = false;

      const DEFAULT_REACTION_HIGHLIGHT_SPACING = 15;
      const DEFAULT_REACTION_HIGHLIGHT_X_OFFSET = 0;
      const DEFAULT_REACTION_HIGHLIGHT_Y_OFFSET = 0;
      const DEFAULT_REACTION_HIGHLIGHT_DISTRIBUTE_CAMERAS = false;

      const CELL_ASPECT_RATIO = 16 / 9;
      const REACTION_CAMERA_CROP_RATIO = 3/4; 
      const HIGHLIGHT_CAMERA_CROP_RATIO = 3/4; 
      const REACTION_HIGHLIGHT_ASPECT_RATIO = 16 / 9;

      
      let initialSelectedSourceCreationScenes = [];


      
      window.translation = {};
      window.miscTranslations = {};
      
      
      function getTranslation(key, params = null) {
        if (!window.translation) {
          
          if (typeof params === 'string') return params;
          return key;
        }
        
        
        const keys = key.split('.');
        let value = window.translation;
        
        for (const k of keys) {
          if (value && typeof value === 'object' && k in value) {
            value = value[k];
          } else {
            
            if (typeof params === 'string') return params;
            return key;
          }
        }
        
        
        if (!value) {
          if (typeof params === 'string') return params;
          return key;
        }
        
        
        if (params && typeof params === 'object') {
          let result = value;
          Object.keys(params).forEach(param => {
            result = result.replace(new RegExp(`{{${param}}}`, 'g'), params[param]);
          });
          return result;
        }
        
        return value || key;
      }
      
      
      async function initializeApp() {
        let savedLanguage = localStorage.getItem('vdoNinjaObsControlLanguage') || 'en';
        languageSelector.value = savedLanguage;
        
        
        try {
          const response = await fetch(`./locales/${savedLanguage}.json`);
          if (response.ok) {
            const localeData = await response.json();
            window.translation = localeData;
            window.miscTranslations = localeData;
          } else {
            console.error('Failed to load translations, using fallbacks');
          }
        } catch (err) {
          console.error('Error loading translations:', err);
        }
        
        loadSettings();
        updateContent();
        logMessage("VDO.Ninja OBS Control Dock Initialized. Welcome!", "logMessages.appInitialized");
        updateVdoNinjaButtonState(false);
        toggleVdoNinjaInputs(false);
        setupSecureFieldsBlur();
        startVdoNinjaConnectionMonitor();
        setupLayoutConfigUI();
      }

      
      function updateContent() {
        
        document.querySelectorAll('[data-i18n]').forEach(element => {
          const keyAttr = element.getAttribute('data-i18n');
          const keys = keyAttr.split(';').map(k => k.trim()).filter(k => k);
          keys.forEach(keyInstruction => {
            if (keyInstruction.startsWith('[placeholder]')) {
              const translationKey = keyInstruction.substring('[placeholder]'.length);
              element.placeholder = getTranslation(translationKey);
            } else if (keyInstruction.startsWith('[title]')) {
              const translationKey = keyInstruction.substring('[title]'.length);
              element.title = getTranslation(translationKey);
            } else {
              element.innerHTML = getTranslation(keyInstruction);
            }
          });
        });
        
        
        document.querySelectorAll('[data-translate]').forEach(element => {
          const keyAttr = element.getAttribute('data-translate');
          const keys = keyAttr.split(';').map(k => k.trim()).filter(k => k);
          keys.forEach(keyInstruction => {
            if (keyInstruction.startsWith('[placeholder]')) {
              element.placeholder = getTranslation(keyInstruction.substring('[placeholder]'.length));
            } else if (keyInstruction.startsWith('[title]')) {
              element.title = getTranslation(keyInstruction.substring('[title]'.length));
            } else {
              element.innerHTML = getTranslation(keyInstruction);
            }
          });
        });
        
        document.querySelectorAll('.stream-mapping .mapping-stream-id').forEach(input => {
            input.placeholder = getTranslation('streamIdMappings.streamIdPlaceholder');
            input.title = getTranslation('streamIdMappings.streamIdTooltip');
        });
        document.querySelectorAll('.stream-mapping .mapping-label').forEach(input => {
            input.placeholder = getTranslation('streamIdMappings.labelPlaceholder');
            input.title = getTranslation('streamIdMappings.labelTooltip');
        });
        document.querySelectorAll('.layout-config-item .layout-scene-select option[value=""]').forEach(opt => {
            opt.textContent = getTranslation('sceneLayouts.selectSceneOption');
        });
         document.querySelectorAll('.layout-config-item .layout-type-select option[value=""]').forEach(opt => {
            opt.textContent = getTranslation('sceneLayouts.selectLayoutOption');
        });
        
        document.querySelectorAll('.layout-specific-controls [data-i18n-label]').forEach(label => { 
            if (label.dataset.i18nLabel && window.translation) {
                 label.textContent = getTranslation(label.dataset.i18nLabel);
            }
        });
         document.querySelectorAll('h3[data-i18n-fallback-header]').forEach(header => { 
            if (header.dataset.i18nFallbackHeader && window.translation) {
                 header.textContent = getTranslation(header.dataset.i18nFallbackHeader);
            }
        });

        updateObsConnectButtonText();
        updateVdoNinjaButtonState(vdoNinjaConnected);
        updateStreamList();
        updatePrefixLabels();
        setupCollapsibleTooltips();
        updateSceneDropdowns(); 
        renderLog();
      }

      
      function setupCollapsibleTooltips() {
          document.querySelectorAll(".collapsible").forEach(header => {
              const isCollapsed = header.classList.contains("collapsed");
              const tooltipKey = isCollapsed ? "collapsible.clickToExpand" : "collapsible.clickToCollapse";
              header.style.setProperty('--before-text', `"${getTranslation(tooltipKey)}"`);
              header.setAttribute('aria-label', getTranslation(tooltipKey));
          });
      }

      
      languageSelector.addEventListener('change', async (event) => {
        const newLang = event.target.value;
        
        try {
          const response = await fetch(`./locales/${newLang}.json`);
          if (response.ok) {
            const localeData = await response.json();
            window.translation = localeData;
            window.miscTranslations = localeData;
            localStorage.setItem('vdoNinjaObsControlLanguage', newLang);
            updateContent();
          } else {
            console.error('Failed to load language:', newLang);
          }
        } catch (err) {
          console.error('Error changing language:', err);
        }
      });

      
      const obsWsUrlInput = document.getElementById("obsWsUrl");
      const obsWsPasswordInput = document.getElementById("obsWsPassword");
      const obsConnectBtn = document.getElementById("obsConnectBtn");
      const obsConnectionStatus = document.getElementById("obsConnectionStatus");
      const obsStatusIndicator = document.getElementById("obsStatusIndicator");
      const cameraPrefixInput = document.getElementById("cameraPrefix");
      const reactionSubPrefixInput =document.getElementById("reactionSubPrefix");
      const highlightSubPrefixInput = document.getElementById("highlightSubPrefix");
      const cameraSubPrefixLabelText = document.getElementById("cameraSubPrefixLabelText");
      const reactionSubPrefixLabelText = document.getElementById("reactionSubPrefixLabelText");
      const highlightSubPrefixLabelText = document.getElementById("highlightSubPrefixLabelText");
      const vdoNinjaBaseUrlInput = document.getElementById("vdoNinjaBaseUrl");
      const vdoNinjaRoomInput = document.getElementById("vdoNinjaRoom");
      const vdoNinjaPasswordInput = document.getElementById("vdoNinjaPassword");
      const vdoNinjaStreamIdsInput = document.getElementById("vdoNinjaStreamIds");
      const vdoNinjaIframe = document.getElementById("vdoNinjaIframe");
      const obsSceneNameInput = document.getElementById("obsSceneNameInput"); 
      const sourceCreationScenesContainer = document.getElementById("sourceCreationScenesContainer"); 
      const loadSourceCreationScenesBtn = document.getElementById("loadSourceCreationScenesBtn");
      const sourceSizingSelect = document.getElementById("sourceSizing"); 
      const sourceCodecSelect = document.getElementById("sourceCodec");
      const autoAddSourcesCheckbox = document.getElementById("autoAddSources");
      const autoRemoveSourcesCheckbox = document.getElementById("autoRemoveSources");
      const streamListContainer = document.getElementById("streamList");
      const logArea = document.getElementById("logArea");
      const customCssInput = document.getElementById("customCssInput");
      const customUserCssStyleTag = document.getElementById("customUserCss");

      
      let obs = null;
      let obsConnected = false;
      let vdoNinjaConnected = false;
      let activeStreams = {}; 
      let obsScenes = []; 
      let requestCallbacks = {}; 
      let vdoNinjaLastActivityTime = 0;
      let vdoNinjaConnectionCheckTimer = null;
      let screenShareId = null; 
      let highlightedStreamId = null; 
      const layoutConfigsListDiv = document.getElementById('layoutConfigsList');
      const addLayoutConfigBtn = document.getElementById('addLayoutConfigBtn');
      let sceneLayoutConfigs = []; 

      
      document.querySelectorAll(".collapsible").forEach((header) => {
        const content = header.nextElementSibling;
        const startsExpanded = header.dataset.state === "expand";
        if (startsExpanded) {
          header.classList.remove("collapsed");
          content.classList.remove("collapsed");
          header.setAttribute("data-state", "collapse");
        } else {
          header.classList.add("collapsed");
          content.classList.add("collapsed");
          header.setAttribute("data-state", "expand");
        }
        header.addEventListener("click", function () {
          const isCollapsed = header.classList.toggle("collapsed");
          content.classList.toggle("collapsed");
          const newState = isCollapsed ? "expand" : "collapse";
          header.setAttribute("data-state", newState);
          setupCollapsibleTooltips(); 
        });
      });

      
      const vdoNinjaConnectBtn = document.createElement("button");
      vdoNinjaConnectBtn.id = "vdoNinjaConnectBtn";
      vdoNinjaConnectBtn.style.marginTop = "5px";
      const vdoNinjaStatusIndicator = document.createElement("span");
      vdoNinjaStatusIndicator.id = "vdoNinjaStatusIndicator";
      vdoNinjaStatusIndicator.className = "status-indicator";
      const vdoNinjaConnectionStatus = document.createElement("span");
      vdoNinjaConnectionStatus.id = "vdoNinjaConnectionStatus";
      vdoNinjaConnectionStatus.style.marginLeft = "5px";
      const vdoNinjaSettingsContainer = document.querySelector(
        "div.container:nth-of-type(3) .collapsible-content" 
      );
      const buttonsDiv = document.createElement("div");
      buttonsDiv.style.marginTop = "10px";
      buttonsDiv.className = "status-line";
      buttonsDiv.appendChild(vdoNinjaConnectBtn);
      buttonsDiv.appendChild(vdoNinjaConnectionStatus);
      buttonsDiv.appendChild(vdoNinjaStatusIndicator);
      if (vdoNinjaSettingsContainer) {
        vdoNinjaSettingsContainer.appendChild(buttonsDiv);
      } else {
        console.error("VDO.Ninja settings container not found for injecting connect button.");
      }
      vdoNinjaConnectBtn.addEventListener("click", () => {
        if (vdoNinjaConnected) {
          disconnectFromVdoNinja();
        } else {
          connectToVdoNinja();
        }
      });

      
      function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
      }

      
      function logMessage(fallbackMessage, i18nKey, i18nParams = {}) {
          const timestamp = new Date().toLocaleTimeString();
          logEntries.push({
              timestamp,
              i18nKey,
              i18nParams,
              fallbackMessage
          });
          if (logEntries.length > 200) {
            logEntries.shift(); 
          }
          renderLog();
      }

      
      function renderLog() {
          if (!logArea || !window.translation) {
             return;
          }
          let logContent = "";
          logEntries.forEach(entry => {
              let translatedMessage = entry.fallbackMessage;
              if (entry.i18nKey && getTranslation(entry.i18nKey)) {
                  translatedMessage = getTranslation(entry.i18nKey, entry.i18nParams);
              } else if (entry.i18nKey) {
                 
              }
              const safeTranslatedMessage = translatedMessage.replace(/</g, "&lt;").replace(/>/g, "&gt;");
              logContent += `[${entry.timestamp}] ${safeTranslatedMessage}\n`;
          });
          logArea.innerHTML = logContent;
          logArea.scrollTop = logArea.scrollHeight;
      }

      
      function updateLayoutConfigsListEmptyState() {
        if (layoutConfigsListDiv && layoutConfigsListDiv.children.length === 0) {
            layoutConfigsListDiv.classList.add('is-empty');
        } else if (layoutConfigsListDiv) {
            layoutConfigsListDiv.classList.remove('is-empty');
        }
      }

      
      function generateRequestId(type) {
        return `${type}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
      }

      
      function generateUUID() { 
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
      }

      
      function getVdoNinjaBaseUrl() {
        const customUrl = vdoNinjaBaseUrlInput.value.trim();
        return customUrl || "https://vdo.ninja";
      }

      
      function getFullCameraPrefix() {
        return cameraPrefixInput.value.trim();
      }

      
      function getFullReactionPrefix() {
        const camPrefix = getFullCameraPrefix();
        const reactSub = reactionSubPrefixInput.value.trim();
        return reactSub ? `${camPrefix}.${reactSub}` : camPrefix; 
      }

      
      function getFullHighlightPrefix() {
        const camPrefix = getFullCameraPrefix();
        const hlSub = highlightSubPrefixInput.value.trim();
        return hlSub ? `${camPrefix}.${hlSub}` : camPrefix; 
      }

      
      function updatePrefixLabels() {
        if (!window.translation) return;
        const cameraPrefixVal = getFullCameraPrefix();
        const reactionSubPrefixVal = reactionSubPrefixInput.value.trim();
        const highlightSubPrefixVal = highlightSubPrefixInput.value.trim();
        cameraSubPrefixLabelText.textContent = getTranslation("obsConnection.cameraPrefixLabel");
        
        let reactionText = getTranslation("obsConnection.reactionPrefixDynamicLabel", "Reaction prefix: {{prefix}}{{separator}}{{subPrefix}}");
        reactionText = reactionText.replace('{{prefix}}', cameraPrefixVal || "VDO")
                                   .replace('{{separator}}', (cameraPrefixVal && reactionSubPrefixVal) ? "." : "")
                                   .replace('{{subPrefix}}', reactionSubPrefixVal || "");
        reactionSubPrefixLabelText.textContent = reactionText;
        
        let highlightText = getTranslation("obsConnection.highlightPrefixDynamicLabel", "Highlight prefix: {{prefix}}{{separator}}{{subPrefix}}");
        highlightText = highlightText.replace('{{prefix}}', cameraPrefixVal || "VDO")
                                     .replace('{{separator}}', (cameraPrefixVal && highlightSubPrefixVal) ? "." : "")
                                     .replace('{{subPrefix}}', highlightSubPrefixVal || "");
        highlightSubPrefixLabelText.textContent = highlightText;
      }

      
      cameraPrefixInput.addEventListener("input", () => {
        updatePrefixLabels();
        saveSettings();
      });
      reactionSubPrefixInput.addEventListener("input", () => {
        updatePrefixLabels();
        saveSettings();
      });
      highlightSubPrefixInput.addEventListener("input", () => {
        updatePrefixLabels();
        saveSettings();
      });

      
      function toggleVdoNinjaInputs(disabled) {
        vdoNinjaBaseUrlInput.disabled = disabled;
        vdoNinjaRoomInput.disabled = disabled;
        vdoNinjaPasswordInput.disabled = disabled;
        vdoNinjaStreamIdsInput.disabled = disabled;
      }

      
      function updateObsConnectButtonText() {
        if (obsConnectBtn && window.translation) {
            obsConnectBtn.textContent = obsConnected ? getTranslation("obsConnection.disconnectButton") : getTranslation("obsConnection.connectButton");
        }
      }

      
      async function getInputSettingsSafe(sourceName, defaultWidth = 1920, defaultHeight = 1080) {
          try {
              const settings = await sendRequest("GetInputSettings", { inputName: sourceName });
              if (settings && settings.inputSettings) {
                  return {
                      width: settings.inputSettings.width || defaultWidth,
                      height: settings.inputSettings.height || defaultHeight,
                  };
              }
          } catch (e) {
             
          }
          return { width: defaultWidth, height: defaultHeight };
      }

      
      async function hideSourceItem(sceneName, sceneItemId, sourceNameForLog = "Unknown") {
          if (!sceneName || !sceneItemId) return;
          try {
              await sendRequest("SetSceneItemEnabled", {
                  sceneName: sceneName,
                  sceneItemId: sceneItemId,
                  sceneItemEnabled: false
              });
              
          } catch(e) {
              logMessage(`Error hiding source item ${sourceNameForLog} in ${sceneName}: ${e.message}`, "logMessages.errorHidingSource", {sourceName: sourceNameForLog, sceneName, message: e.message});
          }
      }
      
      
      function updateVdoNinjaButtonState(isVdoConnected) {
        vdoNinjaConnected = isVdoConnected;
        if (!window.translation) return;
        if (vdoNinjaConnected) {
          vdoNinjaConnectBtn.textContent = getTranslation("vdoNinjaSettings.disconnectButton");
          vdoNinjaConnectBtn.classList.remove("disconnected");
          vdoNinjaConnectBtn.classList.add("connected");
          vdoNinjaConnectionStatus.textContent = getTranslation("vdoNinjaSettings.statusConnected");
          vdoNinjaStatusIndicator.classList.add("connected");
          vdoNinjaStatusIndicator.classList.remove("error");
        } else {
          vdoNinjaConnectBtn.textContent = getTranslation("vdoNinjaSettings.connectButton");
          vdoNinjaConnectBtn.classList.remove("connected");
          vdoNinjaConnectBtn.classList.add("disconnected");
          vdoNinjaConnectionStatus.textContent = getTranslation("vdoNinjaSettings.statusDisconnected");
          vdoNinjaStatusIndicator.classList.remove("connected", "error");
        }
        toggleVdoNinjaInputs(vdoNinjaConnected);
      }

      
      function applyCustomCss(cssString) {
        if (customUserCssStyleTag) {
            customUserCssStyleTag.textContent = cssString;
        }
      }
      customCssInput.addEventListener('input', () => {
        saveSettings(); 
        logMessage("Custom CSS input changed. It will be applied directly to OBS source settings on next creation/update.", "logMessages.customCssChangedWillApplyToSource");
      });

      
      function saveSettings() {
        const selectedSourceCreationOptions = [];
        const checkboxes = document.querySelectorAll('#sourceCreationScenesContainer input[type="checkbox"]:checked');
        checkboxes.forEach(checkbox => selectedSourceCreationOptions.push(checkbox.value));
        
        initialSelectedSourceCreationScenes = selectedSourceCreationOptions;

        const settings = {
          obsWsUrl: obsWsUrlInput.value,
          obsWsPassword: obsWsPasswordInput.value,
          cameraPrefix: cameraPrefixInput.value,
          reactionSubPrefix: reactionSubPrefixInput.value,
          highlightSubPrefix: highlightSubPrefixInput.value,
          vdoNinjaBaseUrl: vdoNinjaBaseUrlInput.value,
          vdoNinjaRoom: vdoNinjaRoomInput.value,
          vdoNinjaPassword: vdoNinjaPasswordInput.value,
          vdoNinjaStreamIds: vdoNinjaStreamIdsInput.value,
          selectedSourceCreationScenes: selectedSourceCreationOptions, 
          sourceSizing: sourceSizingSelect.value, 
          sourceCodec: sourceCodecSelect.value,
          autoAddSources: autoAddSourcesCheckbox.checked,
          autoRemoveSources: autoRemoveSourcesCheckbox.checked,
          screenShareWidth: document.getElementById("screenShareWidth").value,
          screenShareHeight: document.getElementById("screenShareHeight").value,
          language: languageSelector.value || 'en',
          customCSS: customCssInput.value
        };
        localStorage.setItem("obsNinjaSettings", JSON.stringify(settings));
        const mappings = getStreamMappings();
        localStorage.setItem("obsNinjaStreamMappings", JSON.stringify(mappings));
        saveLayoutConfigs(); 
      }

      
      function disconnectFromVdoNinja() {
        vdoNinjaIframe.src = "about:blank";
        if (vdoNinjaConnectionCheckTimer) {
          clearTimeout(vdoNinjaConnectionCheckTimer);
          vdoNinjaConnectionCheckTimer = null;
        }
        activeStreams = {}; 
        updateStreamList();
        updateVdoNinjaButtonState(false); 
        logMessage("Disconnected from VDO.Ninja.", "logMessages.vdoNinja.disconnected");
      }

      
      function connectToVdoNinja() {
        if (vdoNinjaConnected) {
          logMessage("Already connected to VDO.Ninja.", "logMessages.vdoNinja.alreadyConnected");
          return;
        }
        const room = vdoNinjaRoomInput.value.trim();
        const streamIds = vdoNinjaStreamIdsInput.value.trim();
        if (!room && !streamIds) {
          logMessage(
            "VDO.Ninja Error: Room Name or Stream ID(s) must be provided.",
            "logMessages.vdoNinja.errorRoomOrStreamIdNeeded"
          );
          return;
        }
        initializeVdoNinjaIframe(); 
        if (window.translation) {
            vdoNinjaConnectionStatus.textContent = getTranslation("vdoNinjaSettings.statusConnecting");
            vdoNinjaConnectBtn.textContent = getTranslation("vdoNinjaSettings.cancelButton"); 
        }
        if (vdoNinjaConnectionCheckTimer) {
          clearTimeout(vdoNinjaConnectionCheckTimer);
        }
        vdoNinjaConnectionCheckTimer = setTimeout(() => {
          if (
            !vdoNinjaConnected &&
            Date.now() - vdoNinjaLastActivityTime > 10000 
          ) {
            logMessage(
              "VDO.Ninja connection timed out. No activity received from iframe.",
              "logMessages.vdoNinja.connectionTimeout"
            );
            if (window.translation) {
                vdoNinjaConnectionStatus.textContent = getTranslation("vdoNinjaSettings.statusConnectionFailed");
                vdoNinjaConnectBtn.textContent = getTranslation("vdoNinjaSettings.connectButton");
            }
            vdoNinjaStatusIndicator.classList.add("error");
            toggleVdoNinjaInputs(false); 
          }
        }, 10000); 
        toggleVdoNinjaInputs(true); 
        saveSettings();
      }

      
      function addNewStreamMapping(
        streamId = "",
        label = "",
        sceneName = "",
        matchType = "streamId",
        shouldClone = true,
        shouldSwitch = false
      ) {
        const streamMappingsDiv = document.getElementById("streamMappings");
        const mappingDiv = document.createElement("div");
        mappingDiv.className = "stream-mapping"; 
        mappingDiv.dataset.mappingId = generateUUID(); 
        const initialStreamId =
          streamId instanceof PointerEvent ||
          (typeof streamId === "object" && streamId !== null && streamId.target)
            ? ""
            : streamId;
        mappingDiv.innerHTML = `
              <div style="margin-bottom: 5px;">
                  <label style="font-size: 1em; margin-bottom: 6px; display: block; font-weight:500;" data-i18n="streamIdMappings.ruleTitle">Stream Mapping Rule</label>
                  <div class="flex-row" style="align-items: center; margin-bottom: 6px;">
                      <input type="text" value="${initialStreamId}" class="mapping-stream-id" data-i18n="[placeholder]streamIdMappings.streamIdPlaceholder;[title]streamIdMappings.streamIdTooltip" placeholder="Stream ID" title="Enter the VDO.Ninja Stream ID to match.">
                      <input type="text" value="${label}" class="mapping-label" data-i18n="[placeholder]streamIdMappings.labelPlaceholder;[title]streamIdMappings.labelTooltip" placeholder="Label (Optional)" title="Enter the VDO.Ninja Label to match (optional).">
                      <select class="mapping-match-type" data-i18n="[title]streamIdMappings.matchTypeTooltip" title="How to match stream">
                          <option value="streamId" ${matchType === "streamId" ? "selected" : ""} data-i18n="streamIdMappings.matchType.idOnly">ID Only</option>
                          <option value="label" ${matchType === "label" ? "selected" : ""} data-i18n="streamIdMappings.matchType.labelOnly">Label Only</option>
                          <option value="both" ${matchType === "both" ? "selected" : ""} data-i18n="streamIdMappings.matchType.bothRequired">Both Required</option>
                          <option value="either" ${matchType === "either" ? "selected" : ""} data-i18n="streamIdMappings.matchType.eitherMatch">Either Match</option>
                      </select>
                      <select class="mapping-scene-name" data-i18n="[title]streamIdMappings.targetSceneTooltip" title="Target OBS Scene">
                          <option value="" data-i18n="streamIdMappings.selectSceneOption">Select target scene...</option>
                      </select>
                      <button class="remove-mapping-btn" data-i18n="[title]streamIdMappings.removeRuleTooltip" title="Remove this mapping rule">×</button>
                  </div>
                  <div class="flex-row" style="gap: 15px;">
                      <label class="checkbox-label" style="margin-bottom: 0;">
                          <input type="checkbox" class="mapping-clone-to-main" ${shouldClone ? "checked" : ""}>
                          <span data-i18n="streamIdMappings.cloneToMainSceneLabel">Clone to main scene</span>
                      </label>
                      <label class="checkbox-label" style="margin-bottom: 0;">
                          <input type="checkbox" class="mapping-switch-to-scene" ${shouldSwitch ? "checked" : ""}>
                          <span data-i18n="streamIdMappings.switchToSceneOnAddLabel">Switch to scene on add</span>
                      </label>
                  </div>
                  <small style="color: #8a8a9a; font-size: 0.85em; display: block; margin-top: 4px;" data-i18n="streamIdMappings.ruleDescription">
                      Define how incoming streams are routed to OBS scenes.
                  </small>
              </div>
          `;
        streamMappingsDiv.appendChild(mappingDiv);
        if (window.translation) {
            mappingDiv.querySelectorAll('[data-i18n]').forEach(element => {
                const keyAttr = element.getAttribute('data-i18n');
                const keys = keyAttr.split(';').map(k => k.trim()).filter(k => k);
                keys.forEach(keyInstruction => {
                    if (keyInstruction.startsWith('[placeholder]')) {
                        element.placeholder = getTranslation(keyInstruction.substring('[placeholder]'.length));
                    } else if (keyInstruction.startsWith('[title]')) {
                        element.title = getTranslation(keyInstruction.substring('[title]'.length));
                    } else {
                        element.innerHTML = getTranslation(keyInstruction);
                    }
                });
            });
        }
        const sceneDropdown = mappingDiv.querySelector(".mapping-scene-name");
        populateSceneDropdown(obsScenes, sceneDropdown, "streamIdMappings.selectSceneOption"); 
        if (
          sceneName &&
          obsScenes.some((scene) => scene.sceneName === sceneName)
        ) {
          sceneDropdown.value = sceneName; 
        }
        const removeBtn = mappingDiv.querySelector(".remove-mapping-btn");
        removeBtn.addEventListener("click", () => {
          mappingDiv.remove();
          saveSettings(); 
        });
        const inputs = mappingDiv.querySelectorAll("input, select");
        inputs.forEach((input) => {
          input.addEventListener("change", saveSettings);
        });
      }

      
      function setupStreamMappingUI() {
        const addStreamMappingBtn = document.getElementById(
          "addStreamMappingBtn"
        );
        addStreamMappingBtn.addEventListener("click", () => {
          addNewStreamMapping(); 
        });
        loadStreamMappings(); 
      }

      
      function loadStreamMappings() {
        const mappingsJson = localStorage.getItem("obsNinjaStreamMappings");
        if (mappingsJson) {
          try {
            const mappings = JSON.parse(mappingsJson);
            mappings.forEach((mapping) => {
              addNewStreamMapping(
                mapping.streamId,
                mapping.label,
                mapping.sceneName,
                mapping.matchType,
                mapping.cloneToMain !== undefined ? mapping.cloneToMain : true, 
                mapping.switchToScene !== undefined
                  ? mapping.switchToScene
                  : false 
              );
            });
            if (obsScenes && obsScenes.length > 0) {
              updateSceneDropdowns(); 
            }
             if (window.translation) {
                logMessage(`Loaded ${mappings.length} stream mappings.`, "logMessages.loadedStreamMappings", { count: mappings.length });
            } else {
                console.log(`Loaded ${mappings.length} stream mappings.`);
            }
          } catch (e) {
             if (window.translation) {
                logMessage(
                `Error loading stream mappings from localStorage: ${e.message}.`,
                "logMessages.errorLoadingStreamMappings", { message: e.message }
                );
            } else {
                console.error(`Error loading stream mappings from localStorage: ${e.message}`);
            }
          }
        }
      }

      
      function getStreamMappings() {
        const mappings = [];
        document.querySelectorAll(".stream-mapping").forEach((div) => { 
          const streamIdInput = div.querySelector(".mapping-stream-id");
          const labelInput = div.querySelector(".mapping-label");
          const matchTypeSelect = div.querySelector(".mapping-match-type");
          const sceneNameSelect = div.querySelector(".mapping-scene-name");
          const cloneToMainCheckbox = div.querySelector(
            ".mapping-clone-to-main"
          );
          const switchToSceneCheckbox = div.querySelector(
            ".mapping-switch-to-scene"
          );
          if (
            streamIdInput &&
            labelInput &&
            matchTypeSelect &&
            sceneNameSelect &&
            cloneToMainCheckbox &&
            switchToSceneCheckbox
          ) {
            const streamId = streamIdInput.value.trim();
            const label = labelInput.value.trim();
            const matchType = matchTypeSelect.value;
            const sceneName = sceneNameSelect.value.trim();
            const cloneToMain = cloneToMainCheckbox.checked;
            const switchToScene = switchToSceneCheckbox.checked;
            if (sceneName && (streamId || label)) { 
              mappings.push({
                id: div.dataset.mappingId || generateUUID(), 
                streamId,
                label,
                matchType,
                sceneName,
                cloneToMain,
                switchToScene,
              });
            }
          } else {
            logMessage(
              "Warning: Could not find all expected elements in a stream mapping UI div.",
              "logMessages.warningStreamMappingElementsNotFound"
            );
          }
        });
        return mappings;
      }

      
      function setupLayoutConfigUI() {
          if (addLayoutConfigBtn) {
            addLayoutConfigBtn.addEventListener('click', () => {
                addNewLayoutConfigUI();
                updateLayoutConfigsListEmptyState(); 
            });
          }
          loadLayoutConfigs(); 
      }

      
      function createSliderWithNumberInput(idPrefix, labelKey, min, max, step, value, configId, propertyName) {
          const container = document.createElement('div');
          container.className = 'slider-container';

          const label = document.createElement('label');
          label.htmlFor = `layout-slider-${propertyName}-${configId}`;
          label.textContent = (getTranslation(labelKey) ? getTranslation(labelKey) : labelKey) + ":";
          label.dataset.i18nLabel = labelKey;
          label.className = 'slider-label';

          const slider = document.createElement('input');
          slider.type = 'range';
          slider.id = `layout-slider-${propertyName}-${configId}`;
          slider.min = min;
          slider.max = max;
          slider.step = step;
          slider.value = value;
          slider.dataset.propertyName = propertyName;

          const numberInput = document.createElement('input');
          numberInput.type = 'number';
          numberInput.id = `layout-number-${propertyName}-${configId}`;
          numberInput.min = min;
          numberInput.max = max;
          numberInput.step = step;
          numberInput.value = value;
          numberInput.dataset.propertyName = propertyName;


          slider.addEventListener('input', (event) => {
              numberInput.value = event.target.value;
              saveLayoutConfigsAndUpdateScene(configId);
          });
          numberInput.addEventListener('input', (event) => {
              slider.value = event.target.value;
              saveLayoutConfigsAndUpdateScene(configId);
          });
          numberInput.addEventListener('change', (event) => { 
              if (parseFloat(event.target.value) < min) event.target.value = min;
              if (parseFloat(event.target.value) > max) event.target.value = max;
              slider.value = event.target.value;
              saveLayoutConfigsAndUpdateScene(configId);
          });
          
          container.appendChild(label);
          container.appendChild(slider);
          container.appendChild(numberInput);
          return container;
      }

      
      function saveLayoutConfigsAndUpdateScene(configId) {
          saveLayoutConfigs(); 
          const changedConfig = sceneLayoutConfigs.find(c => c.id === configId);

          if (changedConfig && changedConfig.sceneName) {
              triggerLayoutUpdateForScene(changedConfig.sceneName); 

              
              if (changedConfig.layoutType === 'grid') {
                  const changedGridSceneName = changedConfig.sceneName;

                  sceneLayoutConfigs.forEach(otherConfig => {
                      if (otherConfig.id !== configId && 
                          (otherConfig.layoutType === 'reaction' || otherConfig.layoutType === 'highlight') && 
                          otherConfig.sceneName) {
                          
                          
                          
                          const firstPotentialGridFallback = sceneLayoutConfigs.find(cfg => cfg.layoutType === 'grid' && cfg.sceneName);

                          if (firstPotentialGridFallback && firstPotentialGridFallback.sceneName === changedGridSceneName) {
                              
                              
                              
                              logMessage(`Grid settings for '${changedGridSceneName}' (primary fallback candidate) changed. Triggering update for Reaction/Highlight scene '${otherConfig.sceneName}'.`,
                                         "logMessages.obs.gridFallbackSourceChanged", 
                                         { sourceGridScene: changedGridSceneName, dependentScene: otherConfig.sceneName });
                              triggerLayoutUpdateForScene(otherConfig.sceneName);
                          }
                      }
                  });
              }
          }
      }

      
      function createCheckbox(id, labelKey, checked, configId, propertyName) {
          const labelElement = document.createElement('label');
          labelElement.className = 'checkbox-label';
          
          const checkbox = document.createElement('input');
          checkbox.type = 'checkbox';
          checkbox.id = `layout-${propertyName}-${configId}`;
          checkbox.checked = checked;
          checkbox.dataset.propertyName = propertyName;

          const span = document.createElement('span');
          span.textContent = getTranslation(labelKey) ? getTranslation(labelKey) : labelKey;
          span.dataset.i18nLabel = labelKey; 

          checkbox.addEventListener('change', () => {
              saveLayoutConfigsAndUpdateScene(configId);
          });

          labelElement.appendChild(checkbox);
          labelElement.appendChild(span);
          return labelElement;
      }

      
      function addNewLayoutConfigUI(config = {}) {
          const configId = config.id || generateUUID();
          const configDiv = document.createElement('div');
          configDiv.className = 'layout-config-item'; 
          configDiv.dataset.layoutConfigId = configId;

          const topRow = document.createElement('div');
          topRow.className = 'flex-row';
          topRow.innerHTML = `
              <label for="layout-scene-${configId}" data-i18n="sceneLayouts.sceneLabel">Scene:</label>
              <select id="layout-scene-${configId}" class="layout-scene-select">
                  <option value="" data-i18n="sceneLayouts.selectSceneOption">Select Scene...</option>
              </select>
              <label for="layout-type-${configId}" data-i18n="sceneLayouts.layoutLabel">Layout:</label>
              <select id="layout-type-${configId}" class="layout-type-select">
                  <option value="" data-i18n="sceneLayouts.selectLayoutOption">Select Layout...</option>
                  <option value="grid" data-i18n="sceneLayouts.layoutTypes.grid">Grid</option>
                  <option value="reaction" data-i18n="sceneLayouts.layoutTypes.reaction">Reaction</option>
                  <option value="highlight" data-i18n="sceneLayouts.layoutTypes.highlight">Highlight</option>
              </select>
              <button class="remove-layout-btn" data-i18n="[title]sceneLayouts.removeLayoutTooltip" title="Remove this layout configuration">×</button>
          `;
          configDiv.appendChild(topRow);
          
          const controlsContainer = document.createElement('div');
          controlsContainer.className = 'layout-specific-controls';
          controlsContainer.id = `layout-controls-${configId}`;
          controlsContainer.style.display = 'none'; 
          configDiv.appendChild(controlsContainer);

          layoutConfigsListDiv.appendChild(configDiv);

          const sceneSelect = configDiv.querySelector('.layout-scene-select');
          const layoutSelect = configDiv.querySelector('.layout-type-select');
          
          populateSceneDropdownWithOptionsDisabled(obsScenes, sceneSelect, "sceneLayouts.selectSceneOption", configId);

          
          
          
          
          

          if (config.layoutType) {
            layoutSelect.value = config.layoutType;
            updateLayoutSpecificControls(configId, config.layoutType, controlsContainer, config);
          }
          
          if (window.translation) {
            configDiv.querySelectorAll('[data-i18n]').forEach(element => {
                const keyAttr = element.getAttribute('data-i18n');
                const keys = keyAttr.split(';').map(k => k.trim()).filter(k => k);
                keys.forEach(keyInstruction => {
                    if (keyInstruction.startsWith('[placeholder]')) {
                        element.placeholder = getTranslation(keyInstruction.substring('[placeholder]'.length));
                    } else if (keyInstruction.startsWith('[title]')) {
                        element.title = getTranslation(keyInstruction.substring('[title]'.length));
                    } else {
                        element.innerHTML = getTranslation(keyInstruction);
                    }
                });
            });
          }

          configDiv.querySelector('.remove-layout-btn').addEventListener('click', () => {
              const sceneOfRemovedLayout = configDiv.querySelector('.layout-scene-select').value; 
              configDiv.remove();
              saveLayoutConfigs(); 

              
              if (sceneOfRemovedLayout) {
                  
                  const isSceneStillUsed = sceneLayoutConfigs.some(cfg => 
                      cfg.sceneName === sceneOfRemovedLayout
                  );
                  
                  if (!isSceneStillUsed) {
                      const defaultCheckbox = document.querySelector(`#sourceCreationScenesContainer input[type="checkbox"][value="${CSS.escape(sceneOfRemovedLayout)}"]`);
                      if (defaultCheckbox && !defaultCheckbox.checked) {
                          defaultCheckbox.checked = true;
                          saveSettings(); 
                      }
                  }
              }
              

              if (sceneOfRemovedLayout) { 
                 triggerLayoutUpdateForScene(sceneOfRemovedLayout);
              }
              updateLayoutConfigsListEmptyState(); 
              updateSceneDropdowns(); 
          });

          let previousSceneName = config.sceneName || "";
          sceneSelect.addEventListener('change', (event) => {
              const newSceneName = event.target.value;
              const oldSceneName = previousSceneName;
              
              const currentConfigId = configId;
              const isSceneUsedByOther = sceneLayoutConfigs.some(cfg => 
                  cfg.id !== currentConfigId && cfg.sceneName === newSceneName && newSceneName !== ""
              );

              if (isSceneUsedByOther) {
                  logMessage(`Scene '${newSceneName}' is already configured for another layout. Reverting selection.`, "logMessages.sceneAlreadyConfiguredError", {sceneName: newSceneName});
                  const previousValue = sceneLayoutConfigs.find(c => c.id === currentConfigId)?.sceneName || "";
                  event.target.value = previousValue; 
                  if(window.translation) {
                    alert(getTranslation('sceneLayouts.sceneAlreadyConfiguredAlert', {sceneName: newSceneName}));
                  } else {
                    alert(`Scene '${newSceneName}' is already in use by another layout configuration.`);
                  }
                  return; 
              }

              
              
              if (newSceneName) {
                  const newDefaultCheckbox = document.querySelector(`#sourceCreationScenesContainer input[type="checkbox"][value="${CSS.escape(newSceneName)}"]`);
                  if (newDefaultCheckbox && newDefaultCheckbox.checked) {
                      newDefaultCheckbox.checked = false;
                      saveSettings(); 
                  }
              }
              previousSceneName = newSceneName;
              
              saveLayoutConfigsAndUpdateScene(configId); 

              
              if (oldSceneName && oldSceneName !== newSceneName) {
                  
                  const isOldSceneStillUsed = sceneLayoutConfigs.some(cfg => 
                      cfg.sceneName === oldSceneName
                  );
                  if (!isOldSceneStillUsed) {
                      const oldDefaultCheckbox = document.querySelector(`#sourceCreationScenesContainer input[type="checkbox"][value="${CSS.escape(oldSceneName)}"]`);
                      if (oldDefaultCheckbox && !oldDefaultCheckbox.checked) {
                          oldDefaultCheckbox.checked = true;
                          saveSettings(); 
                      }
                  }
              }
              

              updateSceneDropdowns(); 
          });
          layoutSelect.addEventListener('change', () => {
            updateLayoutSpecificControls(configId, layoutSelect.value, controlsContainer, config); 
            saveLayoutConfigsAndUpdateScene(configId);
          });
          
          if (config.sceneName && obsScenes.some(s => s.sceneName === config.sceneName)) {
            sceneSelect.value = config.sceneName;
            
            
            const defaultCheckbox = document.querySelector(`#sourceCreationScenesContainer input[type="checkbox"][value="${CSS.escape(config.sceneName)}"]`);
            if (defaultCheckbox && defaultCheckbox.checked) {
                defaultCheckbox.checked = false;
                saveSettings(); 
            }
            

          } else if (config.sceneName) {
             logMessage(`Saved scene '${config.sceneName}' for layout not found in current OBS scenes.`, "logMessages.layoutSceneNotFound", {sceneName: config.sceneName});
          }
          
          return configId;
      }

      
      function updateLayoutSpecificControls(configId, layoutType, container, currentConfig = {}) {
          container.innerHTML = ''; 
          if (!layoutType) {
              container.style.display = 'none';
              return;
          }
          container.style.display = 'block';

          if (layoutType === 'grid') {
              container.appendChild(createSliderWithNumberInput(`grid-margin-${configId}`, 'sceneLayouts.controls.margin', 0, 500, 1, currentConfig.margin ?? DEFAULT_GRID_MARGIN, configId, 'margin'));
              container.appendChild(createSliderWithNumberInput(`grid-spacing-${configId}`, 'sceneLayouts.controls.spacing', 0, 200, 1, currentConfig.spacing ?? DEFAULT_GRID_SPACING, configId, 'spacing'));
              container.appendChild(createSliderWithNumberInput(`grid-offsetx-${configId}`, 'sceneLayouts.controls.offsetX', -1000, 1000, 1, currentConfig.offsetX ?? DEFAULT_GRID_X_OFFSET, configId, 'offsetX'));
              container.appendChild(createSliderWithNumberInput(`grid-offsety-${configId}`, 'sceneLayouts.controls.offsetY', -1000, 1000, 1, currentConfig.offsetY ?? DEFAULT_GRID_Y_OFFSET, configId, 'offsetY'));
              container.appendChild(createCheckbox(`grid-split-${configId}`, 'sceneLayouts.controls.gridSplitScreenTwoCameras', currentConfig.splitScreenTwoCameras ?? DEFAULT_GRID_SPLIT_SCREEN, configId, 'splitScreenTwoCameras'));
          } else if (layoutType === 'reaction' || layoutType === 'highlight') {
              container.appendChild(createSliderWithNumberInput(`rh-spacing-${configId}`, 'sceneLayouts.controls.spacing', 0, 200, 1, currentConfig.spacing ?? DEFAULT_REACTION_HIGHLIGHT_SPACING, configId, 'spacing'));
              container.appendChild(createSliderWithNumberInput(`rh-offsetx-${configId}`, 'sceneLayouts.controls.offsetX', -1000, 1000, 1, currentConfig.offsetX ?? DEFAULT_REACTION_HIGHLIGHT_X_OFFSET, configId, 'offsetX'));
              container.appendChild(createSliderWithNumberInput(`rh-offsety-${configId}`, 'sceneLayouts.controls.offsetY', -1000, 1000, 1, currentConfig.offsetY ?? DEFAULT_REACTION_HIGHLIGHT_Y_OFFSET, configId, 'offsetY'));
              container.appendChild(createCheckbox(`rh-distribute-${configId}`, 'sceneLayouts.controls.distributeCameras', currentConfig.distributeCameras ?? DEFAULT_REACTION_HIGHLIGHT_DISTRIBUTE_CAMERAS, configId, 'distributeCameras'));
          }
          
          if (window.translation) {
            container.querySelectorAll('[data-i18n-label]').forEach(label => {
                if(label.dataset.i18nLabel && getTranslation(label.dataset.i18nLabel)) {
                    label.textContent = getTranslation(label.dataset.i18nLabel) + ":";
                }
            });
             container.querySelectorAll('.checkbox-label span[data-i18n-label]').forEach(span => {
                 if(span.dataset.i18nLabel && getTranslation(span.dataset.i18nLabel)) {
                    span.textContent = getTranslation(span.dataset.i18nLabel);
                 }
            });
          }
      }

      
      function saveLayoutConfigs() {
          const configs = [];
          let sceneNameError = false;
          const usedSceneNames = new Set();

          document.querySelectorAll('.layout-config-item').forEach(div => {
              const configId = div.dataset.layoutConfigId;
              const sceneSelect = div.querySelector(`#layout-scene-${configId}`);
              const layoutSelect = div.querySelector(`#layout-type-${configId}`);
              
              if (sceneSelect && layoutSelect && sceneSelect.value && layoutSelect.value) {
                  const sceneName = sceneSelect.value;
                  if (usedSceneNames.has(sceneName)) {
                      logMessage(`Error saving layouts: Scene '${sceneName}' is configured multiple times. Please ensure each scene has only one layout.`, "logMessages.errorSavingLayoutsDuplicateScene", {sceneName});
                      sceneNameError = true;
                  }
                  usedSceneNames.add(sceneName);

                  const currentConfig = {
                      id: configId,
                      sceneName: sceneName,
                      layoutType: layoutSelect.value
                  };

                  const controlsContainer = div.querySelector(`#layout-controls-${configId}`);
                  if (controlsContainer) {
                      controlsContainer.querySelectorAll('input[type="range"], input[type="number"], input[type="checkbox"]').forEach(input => {
                          const propertyName = input.dataset.propertyName;
                          if (propertyName) { 
                              if (input.type === 'checkbox') {
                                  currentConfig[propertyName] = input.checked;
                              } else if (input.type === 'number' || input.type === 'range') {
                                  currentConfig[propertyName] = parseFloat(input.value); 
                                  if (isNaN(currentConfig[propertyName])) { 
                                      if (input.type === 'range') currentConfig[propertyName] = parseFloat(input.min); 
                                      else currentConfig[propertyName] = 0; 
                                  }
                              }
                          }
                      });
                  }
                  configs.push(currentConfig);
              }
          });

          if (sceneNameError) {
              console.warn("Duplicate scene names found in layout configurations. Saving anyway, but this might lead to unexpected behavior.");
          }

          sceneLayoutConfigs = configs; 
          localStorage.setItem('obsNinjaSceneLayoutConfigs', JSON.stringify(configs));
          if (!sceneNameError) {
            logMessage(`Saved ${configs.length} scene layout configurations.`, "logMessages.savedLayoutConfigs", {count: configs.length});
          }
      }

      
      function loadLayoutConfigs() {
          const savedConfigsJson = localStorage.getItem('obsNinjaSceneLayoutConfigs');
          layoutConfigsListDiv.innerHTML = ''; 
          sceneLayoutConfigs = []; 

          if (savedConfigsJson) {
              try {
                  const savedConfigs = JSON.parse(savedConfigsJson);
                  savedConfigs.forEach(config => {
                      addNewLayoutConfigUI(config); 
                  });
                  sceneLayoutConfigs = savedConfigs; 
                  logMessage(`Loaded ${savedConfigs.length} scene layout configurations.`, "logMessages.loadedLayoutConfigs", {count: savedConfigs.length});
              } catch (e) {
                  logMessage(`Error loading scene layout configurations: ${e.message}`, "logMessages.errorLoadingLayoutConfigs", {message: e.message});
              }
          }
          updateLayoutConfigsListEmptyState(); 
          updateSceneDropdowns(); 
      }

      
      function getLayoutConfigForScene(sceneName) {
          return sceneLayoutConfigs.find(config => config.sceneName === sceneName);
      }

      
      function loadSettings() {
        const settingsJson = localStorage.getItem("obsNinjaSettings");
        let savedLanguage = 'en'; 
        if (settingsJson) {
          try {
            const settings = JSON.parse(settingsJson);
            obsWsUrlInput.value = settings.obsWsUrl || "ws://localhost:4455";
            obsWsPasswordInput.value = settings.obsWsPassword || "";
            cameraPrefixInput.value = settings.cameraPrefix || "VDO";
            reactionSubPrefixInput.value = settings.reactionSubPrefix || "Screen";
            highlightSubPrefixInput.value =
              settings.highlightSubPrefix || "Highlight";
            vdoNinjaBaseUrlInput.value =
              settings.vdoNinjaBaseUrl || "https://vdo.ninja";
            vdoNinjaRoomInput.value = settings.vdoNinjaRoom || "";
            vdoNinjaPasswordInput.value = settings.vdoNinjaPassword || "";
            vdoNinjaStreamIdsInput.value = settings.vdoNinjaStreamIds || "";
            
            initialSelectedSourceCreationScenes = settings.selectedSourceCreationScenes || []; 
            
            sourceSizingSelect.value = settings.sourceSizing || "defaultSize";
            sourceCodecSelect.value = settings.sourceCodec || "";
            autoAddSourcesCheckbox.checked =
              settings.autoAddSources !== false; 
            autoRemoveSourcesCheckbox.checked =
              settings.autoRemoveSources !== false; 
            const screenShareWidthInput =
              document.getElementById("screenShareWidth");
            const screenShareHeightInput =
              document.getElementById("screenShareHeight");
            if (screenShareWidthInput)
              screenShareWidthInput.value = settings.screenShareWidth || "1920";
            if (screenShareHeightInput)
              screenShareHeightInput.value =
                settings.screenShareHeight || "1080";
            savedLanguage = settings.language || 'en';
            languageSelector.value = savedLanguage; 
            if (settings.customCSS && customCssInput && customUserCssStyleTag) {
                customCssInput.value = settings.customCSS;
                applyCustomCss(settings.customCSS); 
            }
            if (window.translation) {
                logMessage("Settings loaded from localStorage.", "logMessages.settingsLoaded");
            } else {
                console.log("Settings loaded from localStorage.");
            }
          } catch (e) {
            if (window.translation) {
                logMessage(
                `Error loading settings from localStorage: ${e.message}. Using defaults.`,
                "logMessages.errorLoadingSettings", { message: e.message }
                );
            } else {
                console.error(`Error loading settings from localStorage: ${e.message}. Using defaults.`);
            }
            cameraPrefixInput.value = "VDO";
            reactionSubPrefixInput.value = "Screen";
            highlightSubPrefixInput.value = "Highlight";
            vdoNinjaBaseUrlInput.value = "https://vdo.ninja";
            sourceSizingSelect.value = "defaultSize";
            sourceCodecSelect.value = "";
            initialSelectedSourceCreationScenes = [];
          }
        } else {
           if (window.translation) {
                logMessage("No saved settings found. Using default values.", "logMessages.noSavedSettings");
           } else {
                console.log("No saved settings found. Using default values.");
           }
          cameraPrefixInput.value = "VDO";
          reactionSubPrefixInput.value = "Screen";
          highlightSubPrefixInput.value = "Highlight";
          vdoNinjaBaseUrlInput.value = "https://vdo.ninja";
          sourceSizingSelect.value = "defaultSize";
          sourceCodecSelect.value = "";
          initialSelectedSourceCreationScenes = [];
        }
        setupStreamMappingUI(); 
        return savedLanguage; 
      }

      
      function updateStreamList() {
        if (!window.translation) return; 
        if (Object.keys(activeStreams).length === 0) {
          streamListContainer.innerHTML =
            `<div class="stream-item" style="text-align:center; background-color:transparent; border:none; color:#888;">${getTranslation("activeStreams.noActiveStreams")}</div>`;
          return;
        }
        streamListContainer.innerHTML = ""; 
        for (const streamId in activeStreams) {
          if (Object.hasOwnProperty.call(activeStreams, streamId)) {
            const stream = activeStreams[streamId];
            const streamDiv = document.createElement("div");
            streamDiv.className = "stream-item";
            const targetInfo = getTargetSceneForStream(streamId, stream.label);
            let targetSceneNameForDisplay = targetInfo.scene; 
            let targetSceneTagKey = "activeStreams.mappedSceneTag";
            
            const firstSourceCreationScene = getTargetScene(); 

            if (!targetSceneNameForDisplay) { 
                targetSceneNameForDisplay = firstSourceCreationScene; 
                targetSceneTagKey = targetSceneNameForDisplay ? "activeStreams.defaultSceneTag" : "activeStreams.notSet";
            } else if (targetSceneNameForDisplay === firstSourceCreationScene){
                 targetSceneTagKey = "activeStreams.defaultSceneTag"; 
            }


            const isHighlighted = highlightedStreamId === streamId; 
            const isCurrentlyScreenSharingThisStream = screenShareId === streamId;
            let streamItemHTML = `
                      <div style="font-weight: 600; font-size: 1.05em; color: #E0E0E0;">${
                        stream.label || streamId
                      }</div>
                      <small>${getTranslation("activeStreams.streamIdLabel", {id: streamId})}${
              stream.label ? ` | ${getTranslation("activeStreams.labelLabel", {label: stream.label})}` : ""
            }</small>
                      <small style="display: block; color: #A0A0B0;">
                          → ${getTranslation("activeStreams.targetSceneLabelText", {sceneName: targetSceneNameForDisplay || getTranslation("activeStreams.notSet")})}
                          ${targetSceneNameForDisplay ? `(${getTranslation(targetSceneTagKey)})` : ""}
                      </small>
                      <div style="margin-top: 2px; font-size: 0.9em;">
                        ${
                          stream.sourceCreated
                            ? `<span style="color:#28A745">${getTranslation("activeStreams.addedToObs")}</span>`
                            : `<span style="color:#FFC107">${getTranslation("activeStreams.notInObs")}</span>`
                        }
                      </div>
                      <div style="margin-top: 8px;" class="flex-row">`; 
            streamItemHTML += `
                      <button class="add-stream-btn" data-stream-id="${streamId}"
                          style="background-color: ${
                            stream.sourceCreated ? "#F44336" : "#4C80AF"
                          };">
                              ${
                                stream.sourceCreated
                                  ? getTranslation("activeStreams.buttons.removeFromObs")
                                  : getTranslation("activeStreams.buttons.addToObs")
                              }
                      </button>`;
            if (stream.sourceCreated) { 
              streamItemHTML += `
                          <button class="highlight-btn" data-stream-id="${streamId}"
                              style="background-color: ${
                                isHighlighted ? "#F44336" : "#4CAF50"
                              };">
                                  ${
                                    isHighlighted
                                      ? getTranslation("activeStreams.buttons.unhighlight")
                                      : getTranslation("activeStreams.buttons.highlight")
                                  }
                          </button>`;
            }
            if (stream.sourceCreated) {
              streamItemHTML += `
                          <button class="screen-share-btn" data-stream-id="${streamId}"
                                  style="background-color: ${
                                    isCurrentlyScreenSharingThisStream
                                      ? "#F44336"
                                      : "#9C27B0"
                                  };">
                              ${
                                isCurrentlyScreenSharingThisStream
                                  ? getTranslation("activeStreams.buttons.stopScreenShare")
                                  : getTranslation("activeStreams.buttons.screenShare")
                              }
                          </button>`;
            }
            streamItemHTML += `</div>`; 
            streamDiv.innerHTML = streamItemHTML;
            streamListContainer.appendChild(streamDiv);
            const addRemoveBtn = streamDiv.querySelector(".add-stream-btn");
            if (addRemoveBtn) {
              addRemoveBtn.addEventListener("click", () => {
                if (stream.sourceCreated) {
                  removeStreamFromObs(streamId);
                } else {
                  addStreamToObs(streamId, stream.label, targetInfo);
                }
              });
            }
            const highlightBtn = streamDiv.querySelector(".highlight-btn");
            if (highlightBtn) {
              highlightBtn.addEventListener("click", async () => {
                await handleHighlightClick(streamId, stream.label, targetInfo);
              });
            }
            const screenShareBtn = streamDiv.querySelector(".screen-share-btn");
            if (screenShareBtn) {
              screenShareBtn.addEventListener("click", async () => {
                await handleScreenShareClick(streamId, stream.label);
              });
            }
          }
        }
      }

      
      async function handleHighlightClick( 
        clickedStreamId,
        clickedStreamLabel,
        clickedTargetInfo
      ) {
        if (!obsConnected || !obs) {
          logMessage("Cannot highlight: Not connected to OBS.", "logMessages.obs.cannotHighlightNotConnected");
          return;
        }

        if (screenShareId) {
          logMessage(
            `A screen share is active (${screenShareId}). Stopping it before highlighting.`,
            "logMessages.obs.stoppingScreenShareForHighlight", { id: screenShareId }
          );
          await removeScreenShareFromObs(screenShareId); 
        }
        await toggleHighlight( 
          clickedStreamId,
          clickedStreamLabel,
          clickedTargetInfo
        ); 
      }

      
      async function handleScreenShareClick(streamId, streamLabel) {
        const clickedStreamIdForScreenShare = streamId;
        const currentActiveScreenShareGlobal = screenShareId; 
        if (currentActiveScreenShareGlobal === clickedStreamIdForScreenShare) {
          await removeScreenShareFromObs(clickedStreamIdForScreenShare);
        } else {
          if (!obsConnected || !obs) {
            logMessage("Cannot start screen share: Not connected to OBS.", "logMessages.obs.cannotScreenShareNotConnected");
            return;
          }
          if (highlightedStreamId) { 
            logMessage(
              `A camera is highlighted (legacy: ${highlightedStreamId}). Unhighlighting it before starting screen share.`,
              "logMessages.obs.unhighlightingForScreenShare", { id: highlightedStreamId }
            );
            const oldHighlightDetails = activeStreams[highlightedStreamId];
            const oldHighlightLabel = oldHighlightDetails
              ? oldHighlightDetails.label
              : "";
            const oldHighlightTargetInfo = getTargetSceneForStream(
              highlightedStreamId,
              oldHighlightLabel
            );
            await toggleHighlight( 
              highlightedStreamId,
              oldHighlightLabel,
              oldHighlightTargetInfo
            ); 
          }
          await addScreenShareToObs(clickedStreamIdForScreenShare, streamLabel);
        }
      }

      
      async function addScreenShareToObs(streamId, streamLabel) {
        const previousGlobalScreenShareId = screenShareId; 
        if (!obsConnected || !obs) {
          logMessage("Cannot add screen share: Not connected to OBS.", "logMessages.obs.cannotAddScreenShareNotConnected");
          updateStreamList(); 
          return;
        }
        const room = vdoNinjaRoomInput.value.trim();
        if (!room) {
          logMessage(
            "Cannot add screen share: VDO.Ninja Room name is required for screen sharing URLs.",
            "logMessages.obs.cannotAddScreenShareNoRoom"
          );
          updateStreamList();
          return;
        }
        screenShareId = streamId; 
        const width =
          parseInt(document.getElementById("screenShareWidth").value) || 1920;
        const height =
          parseInt(document.getElementById("screenShareHeight").value) || 1080;
        
        const reactionLayoutScenes = sceneLayoutConfigs
            .filter(cfg => cfg.layoutType === 'reaction' && cfg.sceneName)
            .map(cfg => cfg.sceneName);

        if (reactionLayoutScenes.length === 0) {
            logMessage("Cannot add screen share: No scenes are configured with a 'Reaction' layout.", "logMessages.obs.cannotAddScreenShareNoReactionLayoutScene");
            screenShareId = previousGlobalScreenShareId;
            updateStreamList();
            return;
        }
        
        const primaryTargetSceneForCreation = reactionLayoutScenes[0]; 

        const sourceName = `${getFullReactionPrefix()}_${streamId}:s`; 
        const baseUrl = getVdoNinjaBaseUrl();
        const selectedCodec = sourceCodecSelect.value;
        let screenShareUrl = `${baseUrl}/?view=${encodeURIComponent(
          streamId
        )}:s&solo&room=${encodeURIComponent(room)}`;
        if (selectedCodec && selectedCodec !== "") {
          screenShareUrl += `&codec=${encodeURIComponent(selectedCodec)}`;
        } else {
          screenShareUrl += `&codec=vp9`; 
        }
        if (vdoNinjaPasswordInput.value) {
          screenShareUrl += `&password=${encodeURIComponent(
            vdoNinjaPasswordInput.value
          )}`;
        }
        screenShareUrl += "&cleanoutput&transparent&proaudio"; 
        const inputSettings = {
          url: screenShareUrl,
          width: width,
          height: height,
          fps: 30, 
          reroute_audio: true,
          restart_when_active: false, 
          shutdown: false, 
          css: customCssInput.value.trim(), 
        };
        logMessage(
          `Adding/Updating screen share source '${sourceName}' (URL: ${screenShareUrl}) to Reaction scenes. Primary creation in '${primaryTargetSceneForCreation}'.`,
          "logMessages.obs.addingUpdatingScreenShareToReactionScenes", { sourceName, url: screenShareUrl, primaryScene: primaryTargetSceneForCreation, count: reactionLayoutScenes.length }
        );
        try {
          const sourcesResponse = await sendRequest("GetInputList", {}, { suppressNotFound: true });
          const existingSourceWithSamePrefix = sourcesResponse.inputs.find(
            (input) =>
              input.inputName.startsWith(getFullReactionPrefix() + "_") &&
              input.inputName.endsWith(":s") && 
              input.inputName !== sourceName && 
              input.inputKind === "browser_source"
          );
          if (existingSourceWithSamePrefix) {
            logMessage(
              `Reconfiguring existing screen share source '${existingSourceWithSamePrefix.inputName}' to be '${sourceName}'.`,
              "logMessages.obs.reconfiguringExistingScreenShare", { oldName: existingSourceWithSamePrefix.inputName, newName: sourceName }
            );
            await sendRequest("SetInputSettings", {
              inputName: existingSourceWithSamePrefix.inputName,
              inputSettings: inputSettings, 
            });
            if (existingSourceWithSamePrefix.inputName !== sourceName) {
              await sendRequest("SetInputName", {
                inputName: existingSourceWithSamePrefix.inputName,
                newInputName: sourceName,
              });
            }
            logMessage(
              `Reconfigured and renamed existing screen share source to '${sourceName}'.`,
              "logMessages.obs.reconfiguredRenamedScreenShare", { sourceName }
            );
          } else {
            const exactSourceExists = sourcesResponse.inputs.find(
              (input) =>
                input.inputName === sourceName &&
                input.inputKind === "browser_source"
            );
            if (exactSourceExists) {
              logMessage(
                `Screen share source '${sourceName}' already exists. Updating its settings.`,
                "logMessages.obs.screenShareExistsUpdating", { sourceName }
              );
              await sendRequest("SetInputSettings", {
                inputName: sourceName,
                inputSettings: inputSettings, 
              });
            } else {
              logMessage(`Creating new screen share source '${sourceName}' in scene '${primaryTargetSceneForCreation}'.`, "logMessages.obs.creatingNewScreenShareInScene", { sourceName, sceneName: primaryTargetSceneForCreation });
              await sendRequest("CreateInput", {
                sceneName: primaryTargetSceneForCreation, 
                inputName: sourceName,
                inputKind: "browser_source",
                inputSettings, 
                sceneItemEnabled: true, 
              });
            }
          }
          
          const scenesForLayoutUpdate = new Set();
          for (const sceneName of reactionLayoutScenes) {
              const sceneItemId = await ensureSourceInScene(sceneName, sourceName, width, height, true); 
              if (sceneItemId) {
                 const transform = calculateTransform("bestFit", width, height, width, height); 
                 await sendRequest("SetSceneItemTransform", {
                    sceneName: sceneName,
                    sceneItemId: sceneItemId,
                    sceneItemTransform: transform,
                 });
                 logMessage(`Ensured and transformed screen share source '${sourceName}' in Reaction scene '${sceneName}'.`, "logMessages.obs.ensuredTransformedScreenShareReaction", { sourceName, sceneName });
                 scenesForLayoutUpdate.add(sceneName);
              }
          }
          scenesForLayoutUpdate.forEach(sName => { if(sName) triggerLayoutUpdateForScene(sName); });

          logMessage(
            `Successfully configured screen share for stream ${streamId} (${streamLabel}).`,
            "logMessages.obs.successConfigScreenShare", { streamId, label: streamLabel }
          );
        } catch (error) {
          logMessage(
            `Error adding/updating screen share source '${sourceName}': ${error.message}.`,
            "logMessages.obs.errorAddingUpdatingScreenShare", { sourceName, message: error.message }
          );
          screenShareId = previousGlobalScreenShareId; 
        } finally {
          updateStreamList(); 
        }
      }
      
      
      async function toggleHighlight( 
        clickedStreamId,
        clickedStreamLabel,
        clickedTargetInfo
      ) {
        if (!obsConnected || !obs) {
          logMessage("Cannot toggle highlight: Not connected to OBS.", "logMessages.obs.cannotToggleHighlightNotConnected");
          return;
        }
        const targetSceneForLayoutCheck = clickedTargetInfo ? clickedTargetInfo.scene : getTargetScene();
        const layoutConfig = getLayoutConfigForScene(targetSceneForLayoutCheck);
        if (layoutConfig && layoutConfig.layoutType === 'highlight') {
            logMessage(`Scene '${targetSceneForLayoutCheck}' uses the automated Highlight Layout. Manual highlight changes will apply, but may interact with the automated layout.`, "logMessages.obs.highlightLayoutActiveManualLegacy", {sceneName: targetSceneForLayoutCheck});
        }

        const previouslyGlobalHighlightedStreamId = highlightedStreamId;
        const camPrefix = getFullCameraPrefix();
        const hlPrefix = getFullHighlightPrefix(); 
        let sceneToUpdateAfterToggle = targetSceneForLayoutCheck; 
        if (
          previouslyGlobalHighlightedStreamId &&
          previouslyGlobalHighlightedStreamId !== clickedStreamId
        ) {
          const oldStreamDetails = activeStreams[previouslyGlobalHighlightedStreamId];
          const oldStreamLabel = oldStreamDetails ? oldStreamDetails.label : "";
          const oldTargetInfo = getTargetSceneForStream(previouslyGlobalHighlightedStreamId, oldStreamLabel);
          const oldHighlightedSourceName = `${hlPrefix}_${previouslyGlobalHighlightedStreamId}`;
          const oldOriginalSourceName = `${camPrefix}_${previouslyGlobalHighlightedStreamId}`;
          try {
            await sendRequest("GetInputSettings", { inputName: oldHighlightedSourceName }, { suppressNotFound: true }); 
            await sendRequest("SetInputName", { inputName: oldHighlightedSourceName, newInputName: oldOriginalSourceName });
            logMessage(`Stream ${previouslyGlobalHighlightedStreamId} unhighlighted (renamed to ${oldOriginalSourceName}).`, "logMessages.obs.streamUnhighlighted", {id: previouslyGlobalHighlightedStreamId, newName: oldOriginalSourceName});
          } catch (e) { 
            logMessage(`Could not unhighlight (rename) ${oldHighlightedSourceName}, it might not exist or another error: ${e.message}`, "logMessages.obs.unhighlightError", {sourceName: oldHighlightedSourceName, message: e.message});
          }
          if (oldTargetInfo && oldTargetInfo.scene) { 
            sceneToUpdateAfterToggle = oldTargetInfo.scene; 
          }
        }
        const clickedOriginalSourceName = `${camPrefix}_${clickedStreamId}`;
        const clickedNewHighlightedSourceName = `${hlPrefix}_${clickedStreamId}`;
        if (previouslyGlobalHighlightedStreamId === clickedStreamId) { 
          try {
            await sendRequest("GetInputSettings", { inputName: clickedNewHighlightedSourceName }, { suppressNotFound: true }); 
            await sendRequest("SetInputName", { inputName: clickedNewHighlightedSourceName, newInputName: clickedOriginalSourceName });
            highlightedStreamId = null; 
            logMessage(`Stream ${clickedStreamId} successfully unhighlighted (renamed to ${clickedOriginalSourceName}).`, "logMessages.obs.streamSuccessfullyUnhighlighted", {id: clickedStreamId, newName: clickedOriginalSourceName});
          } catch (e) {  
            logMessage(`Could not unhighlight (rename) ${clickedNewHighlightedSourceName}, it might not exist or another error: ${e.message}`, "logMessages.obs.unhighlightError", {sourceName: clickedNewHighlightedSourceName, message: e.message});
            highlightedStreamId = null; 
          }
        } else { 
          try {
            await sendRequest("GetInputSettings", { inputName: clickedOriginalSourceName }, { suppressNotFound: true }); 
            await sendRequest("SetInputName", { inputName: clickedOriginalSourceName, newInputName: clickedNewHighlightedSourceName });
            highlightedStreamId = clickedStreamId; 
            logMessage(`Stream ${clickedStreamId} successfully highlighted (renamed to ${clickedNewHighlightedSourceName}).`, "logMessages.obs.streamSuccessfullyHighlighted", {id: clickedStreamId, newName: clickedNewHighlightedSourceName});
          } catch (e) {  
            logMessage(`Could not highlight (rename) ${clickedOriginalSourceName}, it might not exist or another error: ${e.message}`, "logMessages.obs.highlightError", {sourceName: clickedOriginalSourceName, message: e.message});
            if (previouslyGlobalHighlightedStreamId && previouslyGlobalHighlightedStreamId !== clickedStreamId) {
                 highlightedStreamId = previouslyGlobalHighlightedStreamId; 
            } else {
                 highlightedStreamId = null;
            }
          }
        }
        if (sceneToUpdateAfterToggle) {
             await triggerLayoutUpdateForScene(sceneToUpdateAfterToggle);
        }
        const defaultScene = getTargetScene(); 
        if (clickedTargetInfo && clickedTargetInfo.mapping?.cloneToMain && defaultScene && defaultScene !== sceneToUpdateAfterToggle) {
            await triggerLayoutUpdateForScene(defaultScene);
        }
        updateStreamList();
        saveSettings(); 
      }

      
      obsConnectBtn.addEventListener("click", () => {
        if (obsConnected && obs) {
          logMessage("Disconnecting from OBS WebSocket...", "logMessages.obs.disconnecting");
          if (obs) {
            obs.onclose = null; 
            obs.close();
            obs = null;
          }
          onObsDisconnected(); 
        } else {
          connectToOBS();
        }
      });

      
      function getTargetSceneForStream(streamId, streamLabel = "") {
        const mappings = getStreamMappings();
        const defaultTargetScene = getTargetScene(); 
        for (const mapping of mappings) {
          let isMatch = false;
          switch (mapping.matchType) {
            case "streamId":
              isMatch = mapping.streamId && streamId === mapping.streamId;
              break;
            case "label":
              isMatch =
                mapping.label && streamLabel && streamLabel === mapping.label;
              break;
            case "both":
              isMatch =
                mapping.streamId &&
                mapping.label &&
                streamId === mapping.streamId &&
                streamLabel === mapping.label;
              break;
            case "either":
              isMatch =
                (mapping.streamId && streamId === mapping.streamId) ||
                (mapping.label &&
                  streamLabel &&
                  streamLabel === mapping.label);
              break;
          }
          if (isMatch && mapping.sceneName) {
            return { scene: mapping.sceneName, mapping: mapping }; 
          }
        }
        return { scene: defaultTargetScene, mapping: null }; 
      }

      
      function updateSceneDropdowns() {
        if (!window.translation) return;
        
        const sourceCreationContainer = document.getElementById("sourceCreationScenesContainer");
        if (sourceCreationContainer) {
             populateSceneCheckboxes(obsScenes, sourceCreationContainer, initialSelectedSourceCreationScenes);
        }


        document.querySelectorAll(".mapping-scene-name").forEach(select => {
            const oldValue = select.value;
            populateSceneDropdown(obsScenes, select, "streamIdMappings.selectSceneOption");
            if (oldValue && obsScenes.some(s => s.sceneName === oldValue)) select.value = oldValue; else select.value = "";
        });
        
        document.querySelectorAll(".layout-config-item").forEach(layoutItemDiv => {
            const configId = layoutItemDiv.dataset.layoutConfigId;
            const sceneSelect = layoutItemDiv.querySelector(`#layout-scene-${configId}`);
            if (sceneSelect && configId) {
                const savedConfig = sceneLayoutConfigs.find(c => c.id === configId);
                const targetSceneNameFromStorage = savedConfig ? savedConfig.sceneName : null;
                const currentSelectedValueInDropdown = sceneSelect.value;

                populateSceneDropdownWithOptionsDisabled(obsScenes, sceneSelect, "sceneLayouts.selectSceneOption", configId);

                if (targetSceneNameFromStorage && obsScenes.some(s => s.sceneName === targetSceneNameFromStorage) && !sceneSelect.querySelector(`option[value="${targetSceneNameFromStorage}"]`)?.disabled) {
                    sceneSelect.value = targetSceneNameFromStorage;
                } else if (currentSelectedValueInDropdown && obsScenes.some(s => s.sceneName === currentSelectedValueInDropdown)  && !sceneSelect.querySelector(`option[value="${currentSelectedValueInDropdown}"]`)?.disabled) {
                    sceneSelect.value = currentSelectedValueInDropdown;
                } else {
                    sceneSelect.value = ""; 
                }
            }
        });
      }

      
      async function connectToOBS() {
        if (
          cameraPrefixInput.value.trim() === "" 
        ) {
          logMessage(
            "Error: General Camera prefix is required for OBS connection.",
            "logMessages.obs.errorCameraPrefixRequired" 
          );
          obsStatusIndicator.classList.add("error");
          if (window.translation) obsConnectionStatus.textContent = getTranslation("obsConnection.statusErrorCameraPrefixMissing"); 
          return;
        }
        let url = obsWsUrlInput.value.trim();
        const password = obsWsPasswordInput.value;
        if (!url) {
          logMessage("Error: OBS WebSocket URL is required.", "logMessages.obs.errorUrlRequired");
          obsStatusIndicator.classList.add("error");
          if (window.translation) obsConnectionStatus.textContent = getTranslation("obsConnection.statusErrorUrlMissing");
          return;
        }
        if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
          url = "ws://" + url; 
          obsWsUrlInput.value = url; 
        }
        if (window.translation) obsConnectionStatus.textContent = getTranslation("obsConnection.statusConnecting");
        obsStatusIndicator.classList.remove("connected", "error"); 
        logMessage(`Attempting to connect to OBS WebSocket at ${url}...`, "logMessages.obs.attemptingConnection", { url });
        const connectionTimeoutId = setTimeout(() => {
          if (
            obs &&
            obs.readyState !== WebSocket.OPEN &&
            obs.readyState !== WebSocket.CONNECTING
          ) {
            logMessage("OBS WebSocket connection attempt timed out.", "logMessages.obs.connectionTimeout");
            if (obs) {
              try {
                obs.close();
              } catch (e) {} 
              obs = null;
            }
            if (window.translation) {
                obsConnectionStatus.textContent = getTranslation("obsConnection.statusErrorTimeout");
                obsConnectBtn.textContent = getTranslation("obsConnection.connectButton");
            }
            obsStatusIndicator.classList.add("error");
            obsConnectBtn.classList.remove("connected");
            obsConnectBtn.classList.add("disconnected");
          }
        }, 10000); 
        try {
          obs = new WebSocket(url);
          obs.onopen = () => {
            logMessage(
              "OBS WebSocket connection opened. Waiting for Server Hello...",
              "logMessages.obs.connectionOpenedWaitingHello"
            );
          };
          obs.onmessage = async (event) => {
            try {
              const message = JSON.parse(event.data);
              if (message.op === 0) { 
                logMessage("Received Hello from OBS. Sending Identify...", "logMessages.obs.receivedHelloSendingIdentify");
                const identifyPayload = {
                  op: 1, 
                  d: {
                    rpcVersion: 1,
                    eventSubscriptions:
                      (1 << 0) | 
                      (1 << 1) | 
                      (1 << 2) | 
                      (1 << 3) | 
                      (1 << 6) | 
                      (1 << 7) | 
                      (1 << 8) | 
                      (1 << 9),  
                  },
                };
                if (message.d && message.d.authentication) {
                  const { challenge, salt } = message.d.authentication;
                  if (password) {
                    identifyPayload.d.authentication =
                      await generateAuthResponse(password, salt, challenge);
                    logMessage(
                      "Authentication data prepared for Identify message.",
                      "logMessages.obs.authDataPrepared"
                    );
                  } else {
                    logMessage(
                      "Warning: OBS server requires authentication, but no password provided.",
                      "logMessages.obs.warningAuthRequiredNoPassword"
                    );
                  }
                }
                obs.send(JSON.stringify(identifyPayload));
              } else if (message.op === 2) { 
                clearTimeout(connectionTimeoutId); 
                logMessage(
                  "OBS WebSocket Authentication successful! Connection established.",
                  "logMessages.obs.authSuccessConnected"
                );
                obsConnected = true;
                updateObsConnectButtonText();
                obsConnectBtn.classList.add("connected");
                obsConnectBtn.classList.remove("disconnected");
                if (window.translation) obsConnectionStatus.textContent = getTranslation("obsConnection.statusConnected");
                obsStatusIndicator.classList.add("connected");
                obsStatusIndicator.classList.remove("error");
                onObsConnected(); 
              } else if (message.op === 7) { 
                const entry =
                  message.d && message.d.requestId
                    ? requestCallbacks[message.d.requestId]
                    : null;
                if (entry) {
                  const responseData = message.d;
                  const requestStatus = responseData.requestStatus;
                  const requestType =
                    responseData.requestType || entry.requestType; 
                  if (requestStatus && requestStatus.result === true) { 
                    entry.resolve(responseData.responseData || {}); 
                  } else {
                    const errorMessage = requestStatus && requestStatus.comment ? requestStatus.comment : "Unknown error";
                    const errorCode = requestStatus && requestStatus.code ? requestStatus.code : "N/A";
                    const suppressLog =
                      entry.suppressNotFound && (errorCode === 600 || errorCode === 601 || errorCode === 602 || errorCode === 603 || errorCode === 604); 
                    if (!suppressLog) {
                      logMessage(
                        `OBS Request Error (Type: ${requestType}, ID: ${responseData.requestId}): ${errorMessage} (Code: ${errorCode})`,
                        "logMessages.obs.requestError", { type: requestType, id: responseData.requestId, error: errorMessage, code: errorCode }
                      );
                    }
                    entry.reject(
                      new Error(
                        `Request ${requestType} failed: ${errorMessage} (Code: ${errorCode})`
                      )
                    );
                  }
                  delete requestCallbacks[responseData.requestId]; 
                }
              } else if (message.op === 5) { 
                if (message.d && message.d.eventType) {
                    if (message.d.eventType === "SceneListChanged") {
                        logMessage("OBS Event: Scene list changed. Re-fetching scenes.", "logMessages.obs.eventSceneListChanged");
                        fetchObsScenes();
                    }
                    if (["SceneItemCreated", "SceneItemRemoved", "InputRemoved"].includes(message.d.eventType) && message.d.eventData?.sceneName) {
                        triggerLayoutUpdateForScene(message.d.eventData.sceneName);
                    }
                    if (message.d.eventType === "InputNameChanged" && message.d.eventData) {
                        const oldName = message.d.eventData.oldInputName;
                        const newName = message.d.eventData.inputName;
                        logMessage(`OBS Event: Input name changed from '${oldName}' to '${newName}'. Checking relevant scenes for layout updates.`, "logMessages.obs.inputNameChanged", {oldName, newName});
                        const affectedScenes = new Set();
                        const camPrefix = getFullCameraPrefix();
                        const reactionPfx = getFullReactionPrefix();
                        const highlightPfx = getFullHighlightPrefix();
                        const isRelevantName = (name) => {
                            if (!name) return false;
                            return name.startsWith(camPrefix) || 
                                   (reactionPfx && name.startsWith(reactionPfx)) || 
                                   (highlightPfx && name.startsWith(highlightPfx));
                        };
                        if (isRelevantName(oldName) || isRelevantName(newName)) {
                            sceneLayoutConfigs.forEach(config => {
                                if (config.sceneName) { 
                                    affectedScenes.add(config.sceneName);
                                }
                            });
                            const defaultSceneName = getTargetScene();
                            if (defaultSceneName && !getLayoutConfigForScene(defaultSceneName)) {
                                affectedScenes.add(defaultSceneName);
                            }
                        }
                        affectedScenes.forEach(sceneName => {
                            if(sceneName) triggerLayoutUpdateForScene(sceneName);
                        });
                    }
                }
              }
            } catch (error) {
              logMessage(
                `Error processing OBS WebSocket message: ${error.message}. Data: ${event.data}`,
                "logMessages.obs.errorProcessingMessage", { message: error.message, data: event.data }
              );
            }
          };
          obs.onerror = (errorEvent) => {
            clearTimeout(connectionTimeoutId);
            let errorMsg = "Unknown WebSocket error";
            if (errorEvent && errorEvent.message) {
              errorMsg = errorEvent.message;
            } else if (typeof errorEvent === "string") {
              errorMsg = errorEvent;
            }
            logMessage(`OBS WebSocket Error: ${errorMsg}`, "logMessages.obs.webSocketError", { error: errorMsg });
            obsStatusIndicator.classList.add("error");
            if (window.translation) {
                obsConnectionStatus.textContent = getTranslation("obsConnection.statusError");
                obsConnectBtn.textContent = getTranslation("obsConnection.connectButton");
            }
            obsConnectBtn.classList.remove("connected");
            obsConnectBtn.classList.add("disconnected");
            obsConnected = false;
          };
          obs.onclose = (event) => {
            clearTimeout(connectionTimeoutId);
            let reason = "";
            if (window.translation) {
                if (event.code === 4009) { 
                reason = getTranslation("logMessages.obs.authFailedReason"); 
                } else if (event.reason) {
                reason = event.reason;
                } else {
                reason = getTranslation("logMessages.obs.connectionClosedReasonCode", {code: event.code || "Unknown", wasClean: event.wasClean ? "" : getTranslation("logMessages.obs.uncleanDisconnection") });
                }
            } else {
                reason = `Code ${event.code || 'Unknown'}`; 
            }
            logMessage(`OBS WebSocket Connection Closed. Reason: ${reason}`, "logMessages.obs.connectionClosed", { reason });
            onObsDisconnected(); 
          };
        } catch (error) {
          clearTimeout(connectionTimeoutId);
          logMessage(
            `Error creating OBS WebSocket connection: ${error.message}`,
            "logMessages.obs.errorCreatingConnection", { message: error.message }
          );
          if (window.translation) {
            obsConnectionStatus.textContent = getTranslation("obsConnection.statusError");
            obsConnectBtn.textContent = getTranslation("obsConnection.connectButton");
          }
          obsStatusIndicator.classList.add("error");
          obsConnectBtn.classList.remove("connected");
          obsConnectBtn.classList.add("disconnected");
          obsConnected = false;
        }
      }

      
      async function generateAuthResponse(password, salt, challenge) {
        const encoder = new TextEncoder();
        try {
          const secretString = password + salt;
          const secretData = encoder.encode(secretString);
          let secretHash;
          if (window.crypto && window.crypto.subtle) {
            const hashBuffer = await window.crypto.subtle.digest(
              "SHA-256",
              secretData
            );
            secretHash = new Uint8Array(hashBuffer);
          } else {
            await loadJsShaLibrary(); 
            const shaObj = new jsSHA("SHA-256", "TEXT", { encoding: "UTF8" });
            shaObj.update(secretString);
            const hashHex = shaObj.getHash("HEX");
            secretHash = new Uint8Array(
              hashHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
            );
          }
          const secretBase64 = btoa(String.fromCharCode.apply(null, secretHash));
          const authString = secretBase64 + challenge;
          const authData = encoder.encode(authString);
          let authHash;
          if (window.crypto && window.crypto.subtle) {
            const hashBuffer = await window.crypto.subtle.digest(
              "SHA-256",
              authData
            );
            authHash = new Uint8Array(hashBuffer);
          } else {
            const shaObj = new jsSHA("SHA-256", "TEXT", { encoding: "UTF8" });
            shaObj.update(authString);
            const hashHex = shaObj.getHash("HEX");
            authHash = new Uint8Array(
              hashHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
            );
          }
          return btoa(String.fromCharCode.apply(null, authHash));
        } catch (error) {
          logMessage(`OBS Authentication generation error: ${error.message}`, "logMessages.obs.authGenerationError", { message: error.message });
          throw error; 
        }
      }

      
      function sendRequest(requestType, requestData = {}, options = {}) {
        return new Promise((resolve, reject) => {
          if (!obsConnected || !obs) {
            logMessage(
              `Cannot send request '${requestType}': Not connected to OBS.`,
              "logMessages.obs.cannotSendRequestNotConnected", { requestType }
            );
            reject(new Error("Not connected to OBS"));
            return;
          }
          const requestId = generateRequestId(requestType); 
          requestCallbacks[requestId] = {
            resolve,
            reject,
            requestType,
            suppressNotFound: !!options.suppressNotFound, 
          };
          const requestPayload = {
            op: 6, 
            d: {
              requestType,
              requestId,
              requestData,
            },
          };
          try {
            obs.send(JSON.stringify(requestPayload));
          } catch (error) {
            logMessage(
              `Error sending OBS request '${requestType}': ${error.message}`,
              "logMessages.obs.errorSendingRequest", { requestType, message: error.message }
            );
            if (requestCallbacks[requestId]) {
              const failedEntry = requestCallbacks[requestId];
              delete requestCallbacks[requestId];
              failedEntry.reject(error);
            } else {
              reject(error); 
            }
          }
          setTimeout(() => {
            if (requestCallbacks[requestId]) {
              const entry = requestCallbacks[requestId];
              delete requestCallbacks[requestId]; 
              logMessage(
                `OBS Request '${entry.requestType}' (ID: ${requestId}) timed out.`,
                "logMessages.obs.requestTimeout", { requestType: entry.requestType, id: requestId }
              );
              entry.reject(new Error(`Request timeout for ${entry.requestType}`));
            }
          }, 5000); 
        });
      }

      
      async function onObsConnected() {
        logMessage("OBS Connection fully established. Fetching initial data...", "logMessages.obs.connectionEstablishedFetchingData");
        try {
            await fetchObsScenes(); 

            const inputsResponse = await sendRequest("GetInputList", { inputKind: "browser_source" });
            const hlPrefix = getFullHighlightPrefix();
            let foundHighlightedIdOnConnect = null;

            if (inputsResponse && inputsResponse.inputs) {
                for (const input of inputsResponse.inputs) {
                    if (hlPrefix && input.inputName.startsWith(hlPrefix + "_")) { 
                        const potentialStreamId = input.inputName.substring((hlPrefix + "_").length);
                        if (!potentialStreamId.endsWith(':s') && potentialStreamId.length > 0) {
                            foundHighlightedIdOnConnect = potentialStreamId;
                            logMessage(`Found pre-existing highlighted source in OBS: '${input.inputName}'. Setting active highlight to stream ID: ${foundHighlightedIdOnConnect}.`, "logMessages.obs.foundPreExistingHighlight", {sourceName: input.inputName, streamId: foundHighlightedIdOnConnect});
                            break; 
                        }
                    }
                }
            }
            
            highlightedStreamId = foundHighlightedIdOnConnect; 
            updateStreamList(); 

            sceneLayoutConfigs.forEach(config => {
                if (config.sceneName && config.layoutType) {
                    triggerLayoutUpdateForScene(config.sceneName);
                }
            });
            const mainTargetScene = getTargetScene(); 
            if (mainTargetScene && !getLayoutConfigForScene(mainTargetScene) && sourceSizingSelect.value === "autoGrid") { 
                 triggerLayoutUpdateForScene(mainTargetScene);
            }
          } catch (error) {
            logMessage(`Error during post-OBS connection setup: ${error.message}`, "logMessages.obs.errorPostConnectionSetup", { message: error.message });
          }
      }

      
      function onObsDisconnected() {
        logMessage("OBS Connection has been closed or lost.", "logMessages.obs.connectionClosedOrLost");
        obsConnected = false;
        updateObsConnectButtonText();
        obsConnectBtn.classList.remove("connected");
        obsConnectBtn.classList.add("disconnected");
        if (window.translation) obsConnectionStatus.textContent = getTranslation("obsConnection.statusDisconnected");
        obsStatusIndicator.classList.remove("connected", "error");
        Object.values(activeStreams).forEach(
          (stream) => (stream.sourceCreated = false)
        );
        updateStreamList(); 
        obsScenes = [];
        updateSceneDropdowns();
      }

      
      async function fetchObsScenes() {
        if (!obsConnected || !obs) {
          logMessage("Cannot fetch OBS scenes: Not connected to OBS.", "logMessages.obs.cannotFetchScenesNotConnected");
          return;
        }
        logMessage("Fetching OBS scenes...", "logMessages.obs.fetchingScenes");
        try {
          const response = await sendRequest("GetSceneList");
          if (response && response.scenes) {
            obsScenes = response.scenes; 
            logMessage(`Fetched ${obsScenes.length} scenes from OBS.`, "logMessages.obs.fetchedScenesCount", { count: obsScenes.length });
            updateSceneDropdowns(); 
          } else {
            logMessage("Failed to fetch OBS scenes or no scenes returned.", "logMessages.obs.failedToFetchScenes");
            obsScenes = [];
            updateSceneDropdowns();
          }
        } catch (error) {
          logMessage(`Error fetching OBS scenes: ${error.message}`, "logMessages.obs.errorFetchingScenes", { message: error.message });
          obsScenes = []; 
          updateSceneDropdowns();
        }
      }

      
      function populateSceneCheckboxes(scenesData, containerElement, selectedValues = []) {
          if (!window.translation || !containerElement) return;

          containerElement.innerHTML = ''; 

          if (!scenesData || scenesData.length === 0) {
              const noScenesText = getTranslation("obsTargetSettings.noScenesFound", "Nenhuma cena encontrada");
              containerElement.innerHTML = `<span data-i18n="obsTargetSettings.noScenesFound">${noScenesText}</span>`;
              return;
          }

          scenesData.forEach((scene, index) => {
              const sceneId = `scene-checkbox-${scene.sceneName.replace(/[^a-zA-Z0-9-_]/g, '')}-${index}`; 

              const rowDiv = document.createElement('div');
              rowDiv.className = 'checkbox-item-row';

              const checkbox = document.createElement('input');
              checkbox.type = 'checkbox';
              checkbox.id = sceneId;
              checkbox.value = scene.sceneName;
              checkbox.name = 'sourceCreationScene'; 
              if (selectedValues.includes(scene.sceneName)) {
                  checkbox.checked = true;
              }

              checkbox.addEventListener('change', () => {
                  saveSettings(); 
                  updateStreamList(); 

                  const allCheckedCheckboxes = Array.from(containerElement.querySelectorAll('input[type="checkbox"]:checked'));
                  if (allCheckedCheckboxes.length > 0) {
                      const primarySceneForLayoutUpdate = allCheckedCheckboxes[0].value;
                      if (primarySceneForLayoutUpdate && !getLayoutConfigForScene(primarySceneForLayoutUpdate)) {
                          triggerLayoutUpdateForScene(primarySceneForLayoutUpdate);
                      }
                  }
              });

              const label = document.createElement('label');
              label.htmlFor = sceneId;
              label.textContent = scene.sceneName;

              rowDiv.appendChild(checkbox);
              rowDiv.appendChild(label);
              containerElement.appendChild(rowDiv);
          });
      }


      
      function populateSceneDropdown(
        scenesData, 
        selectElement,
        placeholderKey = "obsTargetSettings.selectSceneOption", 
        isMultiSelect = false, 
        selectedValues = [] 
      ) {
        if (!window.translation || !selectElement) return; 
        
        if (selectElement.id === "sourceCreationScenesContainer") {
            return;
        }

        const currentValues = isMultiSelect ? Array.from(selectElement.selectedOptions).map(opt => opt.value) : [selectElement.value];
        selectElement.innerHTML = ''; 

        if (placeholderKey && !isMultiSelect) { 
            const placeholderOption = document.createElement('option');
            placeholderOption.value = ""; 
            placeholderOption.textContent = getTranslation(placeholderKey);
            selectElement.appendChild(placeholderOption);
        }

        if (scenesData && scenesData.length > 0) {
          scenesData.forEach((scene) => {
            const option = document.createElement("option");
            option.value = scene.sceneName;
            option.textContent = scene.sceneName;
            if (isMultiSelect && selectedValues.includes(scene.sceneName)) { 
                option.selected = true;
            }
            selectElement.appendChild(option);
          });
        }
        
        if (isMultiSelect) {
             Array.from(selectElement.options).forEach(opt => {
                if (currentValues.includes(opt.value)) {
                    opt.selected = true;
                }
            });
        } else { 
            if (currentValues[0] && Array.from(selectElement.options).some(opt => opt.value === currentValues[0])) {
              selectElement.value = currentValues[0];
            } else if (!placeholderKey) { 
                if (selectElement.options.length > 0) selectElement.value = selectElement.options[0].value;
            } else {
               selectElement.value = ""; 
            }
        }
      }

      
      function populateSceneDropdownWithOptionsDisabled(scenesData, selectElement, placeholderKey, currentEditingConfigId) {
          if (!window.translation || !selectElement) return;
          const currentValue = selectElement.value;
          selectElement.innerHTML = '';

          const placeholderOption = document.createElement('option');
          placeholderOption.value = "";
          placeholderOption.textContent = getTranslation(placeholderKey);
          selectElement.appendChild(placeholderOption);

          const configuredScenesByOthers = new Set();
          sceneLayoutConfigs.forEach(cfg => {
              if (cfg.id !== currentEditingConfigId && cfg.sceneName && cfg.sceneName !== "") {
                  configuredScenesByOthers.add(cfg.sceneName);
              }
          });
          
          if (scenesData && scenesData.length > 0) {
              scenesData.forEach((scene) => {
                  const option = document.createElement("option");
                  option.value = scene.sceneName;
                  option.textContent = scene.sceneName;
                  if (configuredScenesByOthers.has(scene.sceneName)) {
                      option.disabled = true;
                      option.textContent += ` (${getTranslation('sceneLayouts.sceneAlreadyConfiguredTooltip', 'Already in use')})`;
                  }
                  selectElement.appendChild(option);
              });
          }

          if (currentValue && Array.from(selectElement.options).some(opt => opt.value === currentValue && !opt.disabled)) {
              selectElement.value = currentValue;
          } else {
              selectElement.value = ""; 
          }
      }

      
      function getTargetScene() { 
        const firstCheckedCheckbox = document.querySelector('#sourceCreationScenesContainer input[type="checkbox"]:checked');
        return firstCheckedCheckbox ? firstCheckedCheckbox.value : "";
      }

      
      function getVdoNinjaViewUrl(streamId, includeCommonParams = true) {
        const room = vdoNinjaRoomInput.value.trim();
        const ninjaPassword = vdoNinjaPasswordInput.value;
        const baseUrl = getVdoNinjaBaseUrl();
        const selectedCodec = sourceCodecSelect.value;
        let url = `${baseUrl}/?`;
        if (room) {
          url += `view=${encodeURIComponent(
            streamId
          )}&solo&room=${encodeURIComponent(room)}`;
        } else {
          url += `view=${encodeURIComponent(streamId)}`;
        }
        if (ninjaPassword) {
          url += `&password=${encodeURIComponent(ninjaPassword)}`;
        }
        if (selectedCodec && selectedCodec !== "") {
          url += `&codec=${encodeURIComponent(selectedCodec)}`;
        }
        if (includeCommonParams) {
          url +=
            "&cleanoutput&proaudio&ab=160&transparent&autoplay&noheader&webcursor&sl&cover&sl&showmeta"; 
        }
        return url;
      }

      
      function initializeVdoNinjaIframe() {
        const room = vdoNinjaRoomInput.value.trim();
        const streamIdsInput = vdoNinjaStreamIdsInput.value.trim();
        if (!room && !streamIdsInput) {
          logMessage(
            "VDO.Ninja: Room Name or specific Stream ID(s) must be provided to connect.",
            "logMessages.vdoNinja.roomOrStreamIdNeededForConnect"
          );
          updateVdoNinjaButtonState(false); 
          return;
        }
        const baseUrl = getVdoNinjaBaseUrl();
        let vdoNinjaUrl = `${baseUrl}/?`;
        if (room) {
          vdoNinjaUrl += `room=${encodeURIComponent(room)}`;
          if (streamIdsInput) {
            const viewStreamIds = streamIdsInput
              .split(",")
              .map((s) => s.trim())
              .filter((s) => s) 
              .join(",");
            if (viewStreamIds)
              vdoNinjaUrl += `&view=${encodeURIComponent(viewStreamIds)}&solo`; 
          }
        } else if (streamIdsInput) {
          const viewStreamIds = streamIdsInput
            .split(",")
            .map((s) => s.trim())
            .filter((s) => s)
            .join(",");
          if (viewStreamIds) {
            vdoNinjaUrl += `view=${encodeURIComponent(viewStreamIds)}`;
            if (viewStreamIds.includes(",")) vdoNinjaUrl += "&solo"; 
          } else {
            logMessage(
              "VDO.Ninja: Stream IDs provided but were empty after trimming.",
              "logMessages.vdoNinja.streamIdsEmptyAfterTrim"
            );
            updateVdoNinjaButtonState(false);
            return;
          }
        }
        if (vdoNinjaPasswordInput.value) {
          vdoNinjaUrl += `&password=${encodeURIComponent(
            vdoNinjaPasswordInput.value
          )}`;
        }
        vdoNinjaUrl +=
          "&cleanoutput&dataonly&nocursor&nopush&noaudio&novideo&cors=" +
          encodeURIComponent(window.location.origin); 
        logMessage(`Initializing VDO.Ninja iframe with URL: ${vdoNinjaUrl}`, "logMessages.vdoNinja.initializingIframe", { url: vdoNinjaUrl });
        vdoNinjaIframe.src = "about:blank"; 
        vdoNinjaIframe.setAttribute("crossorigin", "anonymous"); 
        setTimeout(() => {
          vdoNinjaIframe.src = vdoNinjaUrl;
          vdoNinjaLastActivityTime = Date.now(); 
        }, 100);
      }

      
      window.addEventListener(
        "message",
        (event) => {
          const expectedOriginBase = getVdoNinjaBaseUrl();
          let parsedExpectedOrigin;
          try {
            parsedExpectedOrigin = new URL(expectedOriginBase);
          } catch (e) {
            logMessage(
              `Invalid VDO.Ninja base URL in settings: ${expectedOriginBase}`,
              "logMessages.vdoNinja.invalidBaseUrl", { url: expectedOriginBase }
            );
            return; 
          }
          if (
            event.origin !== parsedExpectedOrigin.origin || 
            event.source !== vdoNinjaIframe.contentWindow 
          ) {
            return;
          }
          const data = event.data;
          vdoNinjaLastActivityTime = Date.now(); 
          if (!vdoNinjaConnected) {
            vdoNinjaConnected = true;
            updateVdoNinjaButtonState(true); 
            if (vdoNinjaConnectionCheckTimer) {
              clearTimeout(vdoNinjaConnectionCheckTimer); 
              vdoNinjaConnectionCheckTimer = null;
            }
            logMessage("VDO.Ninja iframe connection established and active.", "logMessages.vdoNinja.iframeConnectedActive");
          }
          if (data && data.streamID) {
            const streamId = data.streamID;
            const label = data.label || (window.translation ? getTranslation("vdoNinja.defaultStreamLabel", {id: streamId}) : `Stream ${streamId}`); 
            if (
              (data.action === "view-connection" && data.value === true) || 
              data.action === "guest-connected" || 
              (data.action === "push-connection" && data.value === true) 
            ) {
              logMessage(
                `VDO.Ninja stream connected/active: "${label}" (ID: ${streamId})`,
                "logMessages.vdoNinja.streamConnectedActive", { label, id: streamId }
              );
              activeStreams[streamId] = {
                label,
                sourceCreated: false, 
                streamId, 
                uuid: data.UUID || null, 
                connected: true, 
              };
              updateStreamList(); 
              if (autoAddSourcesCheckbox.checked) {
                logMessage(`Auto-adding stream ${streamId} to OBS.`, "logMessages.vdoNinja.autoAddingStream", { id: streamId });
                const targetInfo = getTargetSceneForStream(streamId, label);
                addStreamToObs(streamId, label, targetInfo); 
              }
            } else if (
              (data.action === "view-connection" && data.value === false) || 
              (data.action === "push-connection" && data.value === false) 
            ) {
              if (activeStreams[streamId]) {
                logMessage(
                  `VDO.Ninja stream disconnected/inactive: "${label}" (ID: ${streamId})`,
                  "logMessages.vdoNinja.streamDisconnectedInactive", { label, id: streamId }
                );
                if (
                  autoRemoveSourcesCheckbox &&
                  autoRemoveSourcesCheckbox.checked
                ) {
                  logMessage(`Auto-removing stream ${streamId} from OBS.`, "logMessages.vdoNinja.autoRemovingStream", { id: streamId });
                  removeStreamFromObs(streamId); 
                }
                delete activeStreams[streamId]; 
                updateStreamList(); 
              }
            } else if (
              data.action === "view-connection-info" &&
              data.value &&
              data.value.label 
            ) {
              if (activeStreams[streamId]) {
                if (activeStreams[streamId].label !== data.value.label) {
                  logMessage(
                    `VDO.Ninja stream label updated for ID ${streamId}: "${data.value.label}" (was "${activeStreams[streamId].label}")`,
                    "logMessages.vdoNinja.streamLabelUpdated", { id: streamId, newLabel: data.value.label, oldLabel: activeStreams[streamId].label }
                  );
                  activeStreams[streamId].label = data.value.label;
                  updateStreamList(); 
                }
              }
            }
          }
        },
        false
      );

      
      function startVdoNinjaConnectionMonitor() {
        setInterval(() => {
          if (
            vdoNinjaConnected &&
            Date.now() - vdoNinjaLastActivityTime > 45000 
          ) {
            logMessage(
              "VDO.Ninja connection lost (no activity from iframe). Attempting to reset.",
              "logMessages.vdoNinja.connectionLostResetting"
            );
            if (window.translation) vdoNinjaConnectionStatus.textContent = getTranslation("vdoNinjaSettings.statusConnectionLost");
            vdoNinjaStatusIndicator.classList.add("error");
            vdoNinjaStatusIndicator.classList.remove("connected");
            disconnectFromVdoNinja(); 
          }
        }, 30000); 
      }

      
      async function addStreamToObs(
        streamId,
        streamLabel,
        targetInfo = null 
      ) {
        if (!obsConnected || !obs) {
          logMessage(
            `Cannot add stream "${streamLabel}" (${streamId}) to OBS: Not connected to OBS.`,
            "logMessages.obs.cannotAddStreamNotConnected", { label: streamLabel, id: streamId }
          );
          return;
        }
        const resolvedTargetInfo =
          targetInfo || getTargetSceneForStream(streamId, streamLabel);
        
      const allSelectedSourceCreationScenes = [];
      document.querySelectorAll('#sourceCreationScenesContainer input[type="checkbox"]:checked').forEach(cb => {
          allSelectedSourceCreationScenes.push(cb.value);
      });

      const firstConfiguredLayoutScene = (sceneLayoutConfigs.length > 0 && sceneLayoutConfigs[0].sceneName) ? sceneLayoutConfigs[0].sceneName : "";
      const primaryTargetSceneName = resolvedTargetInfo.scene || (allSelectedSourceCreationScenes.length > 0 ? allSelectedSourceCreationScenes[0] : firstConfiguredLayoutScene) || "";
      const otherScenesToCopyTo = allSelectedSourceCreationScenes.slice(1);


        const mappingRule = resolvedTargetInfo.mapping; 
        if (!primaryTargetSceneName) {
          logMessage(
            `Cannot add stream "${streamLabel}" (${streamId}): Target OBS scene name is required but not set (no default from checkboxes and no mapping).`,
            "logMessages.obs.cannotAddStreamNoTargetSceneName", { label: streamLabel, id: streamId }
          );
          return;
        }

        const baseSourceName = `${getFullCameraPrefix()}_${streamId}`;
        const highlightedSourceName = `${getFullHighlightPrefix()}_${streamId}`;
        let effectiveSourceName = baseSourceName;
        let sourceReallyExistsGlobally = false;

        try {
            await sendRequest("GetInputSettings", { inputName: baseSourceName }, { suppressNotFound: true });
            sourceReallyExistsGlobally = true;
            effectiveSourceName = baseSourceName;
            logMessage(`Found existing standard source '${baseSourceName}' for stream ${streamId}.`, "logMessages.obs.foundExistingStandardSource", { sourceName: baseSourceName, streamId });
        } catch (e_base) {
            if (highlightedSourceName !== baseSourceName) {
                try {
                    await sendRequest("GetInputSettings", { inputName: highlightedSourceName }, { suppressNotFound: true });
                    sourceReallyExistsGlobally = true;
                    effectiveSourceName = highlightedSourceName;
                    if (highlightedStreamId !== streamId) {
                        logMessage(`Found existing OBS source '${highlightedSourceName}' which matches highlight naming for connecting stream ${streamId}. Updating internal highlight state.`, "logMessages.obs.foundExistingHighlightForConnectingStream", { sourceName: highlightedSourceName, streamId });
                        highlightedStreamId = streamId; 
                    }
                    logMessage(`Found existing highlighted source '${highlightedSourceName}' for stream ${streamId}.`, "logMessages.obs.foundExistingHighlightedSource", { sourceName: highlightedSourceName, streamId });
                } catch (e_highlight) {
                    sourceReallyExistsGlobally = false;
                    logMessage(`Neither standard ('${baseSourceName}') nor highlighted ('${highlightedSourceName}') source found for stream ${streamId}. Will create new.`, "logMessages.obs.noExistingSourceFoundWillCreate", { baseSourceName, highlightedSourceName, streamId });
                }
            } else {
                 sourceReallyExistsGlobally = false;
                 logMessage(`Standard source '${baseSourceName}' not found for stream ${streamId} (highlight prefix is same). Will create new.`, "logMessages.obs.noExistingStandardSourceSamePrefix", { sourceName: baseSourceName, streamId });
            }
        }

        if (activeStreams[streamId]) {
            activeStreams[streamId].sourceCreated = sourceReallyExistsGlobally;
        }
        
        const firstSourceCreationSceneFromCheckboxes = getTargetScene(); 
        let shouldCloneThisStreamToMainViaMapping = false;
        if (
          mappingRule &&
          mappingRule.sceneName !== firstSourceCreationSceneFromCheckboxes && 
          mappingRule.cloneToMain &&
          firstSourceCreationSceneFromCheckboxes 
        ) {
          shouldCloneThisStreamToMainViaMapping = true;
        }
        let shouldSwitchToThisScene = false;
        if (mappingRule && mappingRule.switchToScene) {
          shouldSwitchToThisScene = true;
        }
        const vdoNinjaStreamUrl = getVdoNinjaViewUrl(streamId); 
        let canvasWidth = 1920, canvasHeight = 1080; 
        try {
          const videoSettings = await sendRequest("GetVideoSettings");
          if (videoSettings && videoSettings.baseWidth && videoSettings.baseHeight) {
            canvasWidth = videoSettings.baseWidth;
            canvasHeight = videoSettings.baseHeight;
          }
        } catch (error) {
          logMessage(
            `Error getting OBS canvas size: ${error.message}. Using default ${canvasWidth}x${canvasHeight}.`,
            "logMessages.obs.errorGettingCanvasSize", { message: error.message, width: canvasWidth, height: canvasHeight }
          );
        }
        const inputSettings = {
          url: vdoNinjaStreamUrl,
          width: canvasWidth, 
          height: canvasHeight, 
          fps: 30,
          css: customCssInput.value.trim(), 
          reroute_audio: true,
          restart_when_active: false,
          shutdown: false, 
        };
        
        try {
          if (!sourceReallyExistsGlobally) {
            effectiveSourceName = baseSourceName; 
            logMessage(
              `Source '${effectiveSourceName}' does not exist globally. Creating it in scene '${primaryTargetSceneName}'.`,
              "logMessages.obs.sourceNotGlobalCreating", { sourceName: effectiveSourceName, sceneName: primaryTargetSceneName }
            );
            await sendRequest("CreateInput", {
              sceneName: primaryTargetSceneName, 
              inputName: effectiveSourceName, 
              inputKind: "browser_source",
              inputSettings, 
              sceneItemEnabled: true, 
            });
            logMessage(`Global source '${effectiveSourceName}' created and added to scene '${primaryTargetSceneName}'.`, "logMessages.obs.sourceCreatedAddedToScene", {sourceName: effectiveSourceName, sceneName: primaryTargetSceneName});
          } else {
            logMessage(
              `Source '${effectiveSourceName}' already exists globally. Updating its settings. URL: ${vdoNinjaStreamUrl}`,
              "logMessages.obs.sourceGlobalUpdatingWithUrl", { sourceName: effectiveSourceName, url: vdoNinjaStreamUrl }
            );
            await sendRequest("SetInputSettings", {
              inputName: effectiveSourceName, 
              inputSettings, 
            });
          }

          const scenesToEnsureItemIn = new Set();
          
          if (primaryTargetSceneName) {
              scenesToEnsureItemIn.add(primaryTargetSceneName);
          }

          allSelectedSourceCreationScenes.forEach(sceneName => {
              if (sceneName) scenesToEnsureItemIn.add(sceneName);
          });
          
          if (shouldCloneThisStreamToMainViaMapping && firstSourceCreationSceneFromCheckboxes) {
              scenesToEnsureItemIn.add(firstSourceCreationSceneFromCheckboxes);
          }

          const scenesWithLayouts = sceneLayoutConfigs
              .map(config => config.sceneName)
              .filter(Boolean);
          
          scenesWithLayouts.forEach(sceneName => {
              if (sceneName) {
                 logMessage(`Adding source '${effectiveSourceName}' to scene '${sceneName}' because it has a layout configured.`, "logMessages.obs.addingSourceToLayoutScene", {sourceName: effectiveSourceName, sceneName: sceneName});
                 scenesToEnsureItemIn.add(sceneName);
              }
          });

          for (const sceneName of scenesToEnsureItemIn) {
               if (sceneName) {
                  logMessage(`Ensuring source item '${effectiveSourceName}' exists in scene '${sceneName}'.`, "logMessages.obs.ensuringSourceItemInScene", {sourceName: effectiveSourceName, sceneName: sceneName});
                  await ensureSourceInScene(sceneName, effectiveSourceName, canvasWidth, canvasHeight);
               }
          }

          if (activeStreams[streamId]) activeStreams[streamId].sourceCreated = true;

          // Disparar atualizações de layout para todas as cenas afetadas
          scenesToEnsureItemIn.forEach(scene => {
            if (scene) triggerLayoutUpdateForScene(scene);
          });

          if (shouldSwitchToThisScene && mappingRule.sceneName) {
            logMessage(`Switching OBS current program scene to '${mappingRule.sceneName}' due to mapping rule.`, "logMessages.obs.switchingProgramScene", { sceneName: mappingRule.sceneName });
            const studio = await sendRequest("GetStudioModeEnabled");
            if (studio.studioModeEnabled) {
              await sendRequest("SetCurrentPreviewScene", { sceneName: mappingRule.sceneName });
              await sleep(100); 
              await sendRequest("TriggerStudioModeTransition");
            } else {
              await sendRequest("SetCurrentProgramScene", { sceneName: mappingRule.sceneName });
            }
          }
          logMessage(
            `Successfully processed stream "${streamLabel}" (${streamId}), effective OBS source: '${effectiveSourceName}'.`,
            "logMessages.obs.successfullyProcessedStream", { label: streamLabel, id: streamId, sourceName: effectiveSourceName }
          );
        } catch (error) {
          logMessage(
            `Error managing stream '${effectiveSourceName}' ("${streamLabel}") in OBS: ${error.message}`,
            "logMessages.obs.errorManagingStream", { sourceName: effectiveSourceName, label: streamLabel, message: error.message }
          );
          if (activeStreams[streamId]) activeStreams[streamId].sourceCreated = false; 
        } finally {
          updateStreamList(); 
        }
      }

      
      async function ensureSourceInScene(sceneName, sourceName, canvasWidth, canvasHeight, isScreenShare = false) {
          if (!sceneName || !sourceName) return null;
          let sceneItemId = null;
          try {
              const itemInfo = await sendRequest("GetSceneItemId", { sceneName, sourceName }, { suppressNotFound: true });
              sceneItemId = itemInfo.sceneItemId;
              await sendRequest("SetSceneItemEnabled", { sceneName, sceneItemId, sceneItemEnabled: true });
              logMessage(`Source '${sourceName}' found in scene '${sceneName}'. Ensured enabled.`, "logMessages.obs.sourceFoundInSceneEnabled", {sourceName, sceneName, itemId: sceneItemId});
          } catch (e) { 
              const isNotFound = e.message.toLowerCase().includes("not found") || (e.message.toLowerCase().includes("code") && e.message.toLowerCase().includes("600"));
              if (isNotFound) {
                  logMessage(`Source '${sourceName}' not in scene '${sceneName}'. Adding it.`, "logMessages.obs.sourceNotInSceneAdding", {sourceName, sceneName});
                  try {
                      const createItemResponse = await sendRequest("CreateSceneItem", { sceneName, sourceName, sceneItemEnabled: true });
                      sceneItemId = createItemResponse.sceneItemId;
                      logMessage(`Source '${sourceName}' added to scene '${sceneName}', item ID: ${sceneItemId}.`, "logMessages.obs.sourceAddedToScene", {sourceName, sceneName, itemId: sceneItemId});
                  } catch (createError) {
                      logMessage(`Error creating scene item for '${sourceName}' in '${sceneName}': ${createError.message}`, "logMessages.obs.errorCreatingSceneItem", {sourceName, sceneName, message: createError.message});
                      return null; 
                  }
              } else {
                  logMessage(`Error checking for '${sourceName}' in scene '${sceneName}': ${e.message}`, "logMessages.obs.errorCheckingSceneForItem", {sourceName, sceneName, message: e.message});
                  return null; 
              }
          }
          if (sceneItemId) {
             try {
                const sizing = isScreenShare ? "bestFit" : sourceSizingSelect.value;
                const defaultTransform = calculateTransform(sizing, canvasWidth, canvasHeight, canvasWidth, canvasHeight); 
                await sendRequest("SetSceneItemTransform", { sceneName, sceneItemId, sceneItemTransform: defaultTransform });
             } catch (transformError) {
                 logMessage(`Error applying initial default transform to '${sourceName}' in '${sceneName}': ${transformError.message}`, "logMessages.obs.errorApplyingInitialTransform", {sourceName, sceneName, message: transformError.message});
             }
          }
          return sceneItemId;
      }

      
      async function applyTransformAndGrid( 
        sceneName,
        sourceName, 
        canvasWidth,
        canvasHeight,
        sceneItemId = null 
      ) {
        logMessage(`applyTransformAndGrid called for ${sourceName} in ${sceneName}. Triggering full layout update for scene.`, "logMessages.obs.applyTransformAndGridCalled", {sourceName, sceneName});
        await triggerLayoutUpdateForScene(sceneName);
      }

      
      async function triggerLayoutUpdateForScene(sceneName) {
        if (!obsConnected || !obs || !sceneName) {
            return;
        }
        logMessage(`Triggering layout update for scene '${sceneName}'...`, "logMessages.obs.triggeringLayoutUpdate", {sceneName});
        const layoutConfig = getLayoutConfigForScene(sceneName);
        try {
            let canvasWidth = 1920, canvasHeight = 1080;
            const videoSettings = await sendRequest("GetVideoSettings");
            if (videoSettings && videoSettings.baseWidth && videoSettings.baseHeight) {
                canvasWidth = videoSettings.baseWidth;
                canvasHeight = videoSettings.baseHeight;
            }
            const sceneItemsResponse = await sendRequest("GetSceneItemList", { sceneName });
            if (!sceneItemsResponse || !sceneItemsResponse.sceneItems) {
                logMessage(`No scene items found in scene '${sceneName}' for layout update.`, "logMessages.obs.noSceneItemsForLayout", {sceneName});
                return;
            }
            const camPrefix = getFullCameraPrefix();
            const reactionMainPrefix = getFullReactionPrefix();
            const highlightMainPrefix = getFullHighlightPrefix();
            
            const vdoNinjaSceneItems = sceneItemsResponse.sceneItems.filter(item => {
                const sName = item.sourceName;
                const isGeneralCam = sName.startsWith(camPrefix);
                const isReactionMain = reactionMainPrefix && sName.startsWith(reactionMainPrefix) && sName.endsWith(':s'); 
                const isHighlightMain = highlightMainPrefix && sName.startsWith(highlightMainPrefix) && !sName.endsWith(':s'); 

                if (isReactionMain || isHighlightMain) {
                    return true; 
                }
                if (isGeneralCam) {
                    let streamId = null;
                    if (sName.startsWith(camPrefix + "_")) {
                        streamId = sName.substring((camPrefix + "_").length);
                    } else if (sName.startsWith(camPrefix + ".")) { 
                        const parts = sName.split('.');
                        if (parts.length > 1) {
                           const potentialId = parts[parts.length -1];
                           if (activeStreams[potentialId] || activeStreams[potentialId.replace(':s','')]) return true;
                        }
                    } else { 
                        streamId = sName.substring(camPrefix.length);
                    }
                    
                    if (streamId && streamId.endsWith(':s')) streamId = streamId.slice(0, -2); 
                    return streamId && activeStreams[streamId] && activeStreams[streamId].sourceCreated && activeStreams[streamId].connected;
                }
                return false; 
            });

            if (layoutConfig) {
                logMessage(`Applying '${layoutConfig.layoutType}' layout to scene '${sceneName}'. (${vdoNinjaSceneItems.length} items)`, "logMessages.obs.applyingConfiguredLayout", {layoutType: layoutConfig.layoutType, sceneName, count: vdoNinjaSceneItems.length});
                switch (layoutConfig.layoutType) {
                    case 'grid':
                        await applyGridLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig);
                        break;
                    case 'reaction':
                        await applyReactionLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig);
                        break;
                    case 'highlight':
                        await applyHighlightLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig);
                        break;
                    default:
                        logMessage(`Unknown layout type '${layoutConfig.layoutType}' for scene '${sceneName}'. Applying default sizing.`, "logMessages.obs.unknownLayoutType", {layoutType: layoutConfig.layoutType, sceneName});
                        await applyDefaultSizingToSceneItems(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight);
                }
            } else { 
                 logMessage(`No specific layout for scene '${sceneName}'. Applying default source sizing ('${sourceSizingSelect.value}').`, "logMessages.obs.noSpecificLayoutApplyingDefault", {sceneName, sizing: sourceSizingSelect.value});
                 await applyDefaultSizingToSceneItems(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight);
            }
        } catch (error) {
            let errorMessage = error.message || "Unknown error during layout update";
             if (error.description) errorMessage = error.description; 
            logMessage(`Error during layout update for scene '${sceneName}': ${errorMessage}`, "logMessages.obs.errorTriggeringLayoutUpdate", {sceneName, message: errorMessage});
            console.error("Full layout error object:", error);
        }
      }

      
      async function applyDefaultSizingToSceneItems(sceneName, sceneItems, canvasWidth, canvasHeight) {
        logMessage(`Applying default source sizing ('${sourceSizingSelect.value}') to ${sceneItems.length} items in scene '${sceneName}'.`, "logMessages.obs.applyingDefaultSizing", {sizing: sourceSizingSelect.value, count: sceneItems.length, sceneName});
        for (const item of sceneItems) {
            try {
                const inputSettingsResponse = await getInputSettingsSafe(item.sourceName, canvasWidth, canvasHeight); 
                let sourceW = inputSettingsResponse.width; 
                let sourceH = inputSettingsResponse.height;
                
                const transform = calculateTransform(sourceSizingSelect.value, sourceW, sourceH, canvasWidth, canvasHeight);
                await sendRequest("SetSceneItemTransform", { sceneName, sceneItemId: item.sceneItemId, sceneItemTransform: transform });
                 await sendRequest("SetSceneItemEnabled", { sceneName, sceneItemId: item.sceneItemId, sceneItemEnabled: true });
            } catch (e) {
                logMessage(`Error applying default transform to ${item.sourceName} in ${sceneName}: ${e.message}`, "logMessages.obs.errorApplyingDefaultTransformItem", {sourceName: item.sourceName, sceneName, message: e.message});
            }
        }
      }

      
      async function applyGridLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig) {
        const generalCamPrefix = getFullCameraPrefix();
        const cameraItems = vdoNinjaSceneItems.filter(item => item.sourceName.startsWith(generalCamPrefix) && !item.sourceName.endsWith(':s')); 

        if (cameraItems.length === 0) {
            logMessage(`No VDO.Ninja camera sources to apply Grid layout in scene '${sceneName}'.`, "logMessages.obs.noSourcesForGridLayout", {sceneName});
            for (const item of vdoNinjaSceneItems) {
                if (!cameraItems.find(ci => ci.sceneItemId === item.sceneItemId)) {
                    await hideSourceItem(sceneName, item.sceneItemId, item.sourceName);
                }
            }
            return;
        }
        logMessage(`Applying Grid Layout to ${cameraItems.length} VDO.Ninja camera sources in scene '${sceneName}'.`, "logMessages.obs.applyingGridLayoutScene", {count: cameraItems.length, sceneName});
        
        const margin = layoutConfig.margin ?? DEFAULT_GRID_MARGIN;
        const spacing = layoutConfig.spacing ?? DEFAULT_GRID_SPACING;
        const offsetX = layoutConfig.offsetX ?? DEFAULT_GRID_X_OFFSET;
        const offsetY = layoutConfig.offsetY ?? DEFAULT_GRID_Y_OFFSET;
        const splitScreenTwoCameras = layoutConfig.splitScreenTwoCameras ?? DEFAULT_GRID_SPLIT_SCREEN;

        if (splitScreenTwoCameras && cameraItems.length === 2) {
            logMessage(`Applying 2-camera split screen grid layout to scene '${sceneName}'.`, "logMessages.obs.applyingGridSplitScreen", {sceneName});
            const itemWidth = canvasWidth / 2;
            const itemHeight = canvasHeight; 

            for (let i = 0; i < cameraItems.length; i++) {
                const item = cameraItems[i];
                const camSrcInfo = await getInputSettingsSafe(item.sourceName, 1920, 1080);
                
                const sourceAspectRatio = camSrcInfo.width / camSrcInfo.height;
                const targetAspectRatio = itemWidth / itemHeight;

                let cropLeft = 0, cropRight = 0, cropTop = 0, cropBottom = 0;
                let scaledWidth = camSrcInfo.width;
                let scaledHeight = camSrcInfo.height;

                if (sourceAspectRatio > targetAspectRatio) { 
                    scaledHeight = itemHeight;
                    scaledWidth = scaledHeight * sourceAspectRatio;
                    const excessWidth = scaledWidth - itemWidth;
                    cropLeft = Math.round( (excessWidth / 2) / scaledWidth * camSrcInfo.width );
                    cropRight = cropLeft;
                } else { 
                    scaledWidth = itemWidth;
                    scaledHeight = scaledWidth / sourceAspectRatio;
                    const excessHeight = scaledHeight - itemHeight;
                    cropTop = Math.round( (excessHeight / 2) / scaledHeight * camSrcInfo.height);
                    cropBottom = cropTop;
                }
                
                const finalScaleX = itemWidth / (camSrcInfo.width - cropLeft - cropRight);
                const finalScaleY = itemHeight / (camSrcInfo.height - cropTop - cropBottom);

                const transform = {
                    alignment: 5, boundsType: "OBS_BOUNDS_NONE",
                    positionX: i * itemWidth + offsetX,
                    positionY: offsetY,
                    scaleX: finalScaleX, 
                    scaleY: finalScaleY,
                    width: itemWidth, height: itemHeight, 
                    sourceWidth: camSrcInfo.width, sourceHeight: camSrcInfo.height,
                    cropLeft: cropLeft, cropRight: cropRight,
                    cropTop: cropTop, cropBottom: cropBottom,
                };
                await sendRequest("SetSceneItemEnabled", {sceneName, sceneItemId: item.sceneItemId, sceneItemEnabled: true});
                await sendRequest("SetSceneItemTransform", { sceneName, sceneItemId: item.sceneItemId, sceneItemTransform: transform });
            }

        } else {
            const positions = calculateGridPositions( 
                cameraItems.length, canvasWidth, canvasHeight,
                margin, spacing, CELL_ASPECT_RATIO, offsetX, offsetY
            );
            for (let i = 0; i < cameraItems.length; i++) {
                const item = cameraItems[i];
                const gridPos = positions[i];
                if (gridPos) {
                    const camSrcInfo = await getInputSettingsSafe(item.sourceName, 1920, 1080);
                    const transform = calculateTransform(
                        "autoGrid", camSrcInfo.width, camSrcInfo.height, 
                        canvasWidth, canvasHeight, gridPos 
                    );
                    await sendRequest("SetSceneItemEnabled", {sceneName, sceneItemId: item.sceneItemId, sceneItemEnabled: true});
                    await sendRequest("SetSceneItemTransform", {
                      sceneName, sceneItemId: item.sceneItemId, sceneItemTransform: transform,
                    });
                }
            }
        }
        const cameraItemIds = new Set(cameraItems.map(it => it.sceneItemId));
        for (const item of vdoNinjaSceneItems) {
            if (!cameraItemIds.has(item.sceneItemId)) {
                 await hideSourceItem(sceneName, item.sceneItemId, item.sourceName);
            }
        }
      }

      
      async function applyReactionOrHighlightLayoutShared(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig, isReactionLayout) {
        const generalCamPrefix = getFullCameraPrefix();
        const mainContentPrefix = isReactionLayout ? getFullReactionPrefix() : getFullHighlightPrefix();
        const mainContentSuffix = isReactionLayout ? ':s' : ''; 

        const mainContentSources = vdoNinjaSceneItems.filter(item => 
            item.sourceName.startsWith(mainContentPrefix) && 
            (isReactionLayout ? item.sourceName.endsWith(mainContentSuffix) : !item.sourceName.endsWith(':s'))
        );
        const sideCameraSources = vdoNinjaSceneItems.filter(item => 
            item.sourceName.startsWith(generalCamPrefix) && 
            !mainContentSources.find(mcs => mcs.sourceName === item.sourceName) && 
            !item.sourceName.endsWith(':s') 
        );
        
        let mainContentItem = null;
        if (mainContentSources.length > 0) {
            try {
                if (isReactionLayout && screenShareId) {
                    const currentScreenShareSourceName = `${mainContentPrefix}_${screenShareId}${mainContentSuffix}`;
                    const activeScreenShareItem = mainContentSources.find(mcs => mcs.sourceName === currentScreenShareSourceName);
                    if (activeScreenShareItem) {
                         const itemDetails = await sendRequest("GetSceneItemEnabled", { sceneName, sceneItemId: activeScreenShareItem.sceneItemId });
                         if (itemDetails.sceneItemEnabled) mainContentItem = activeScreenShareItem;
                    }
                }
                if (!mainContentItem && mainContentSources[0]) { 
                    
                    
                    const itemDetails = await sendRequest("GetSceneItemEnabled", { sceneName, sceneItemId: mainContentSources[0].sceneItemId });
                    if (itemDetails.sceneItemEnabled) {
                        mainContentItem = mainContentSources[0];
                    }
                }
            } catch (e) {
                logMessage(`Error checking if main content is enabled: ${e.message}`, "logMessages.obs.errorCheckingMainContentEnabled", {sourceName: mainContentSources[0]?.sourceName || 'unknown', message: e.message});
            }
        }


        if (!mainContentItem) { 
            logMessage(`Main content for ${isReactionLayout ? 'Reaction' : 'Highlight'} layout in scene '${sceneName}' not found/visible. Applying fallback grid of cameras.`, 
                       isReactionLayout ? "logMessages.obs.reactionMainNotFoundFallbackGrid" : "logMessages.obs.highlightMainNotFoundFallbackGrid", { sceneName });
            
            let fallbackGridMargin = DEFAULT_GRID_MARGIN;
            let fallbackGridSpacing = DEFAULT_GRID_SPACING;
            let fallbackGridOffsetX = DEFAULT_GRID_X_OFFSET;
            let fallbackGridOffsetY = DEFAULT_GRID_Y_OFFSET;
            let fallbackGridSplitScreenTwoCameras = DEFAULT_GRID_SPLIT_SCREEN;

            const firstGridSceneConfig = sceneLayoutConfigs.find(cfg => cfg.layoutType === 'grid' && cfg.sceneName);

            if (firstGridSceneConfig) {
                logMessage(`Using grid settings from the first configured 'Grid' layout scene ('${firstGridSceneConfig.sceneName}') as fallback for '${sceneName}'.`, "logMessages.obs.usingGridSceneFallbackSettings", { sceneName: firstGridSceneConfig.sceneName, targetScene: sceneName });
                fallbackGridMargin = firstGridSceneConfig.margin ?? DEFAULT_GRID_MARGIN;
                fallbackGridSpacing = firstGridSceneConfig.spacing ?? DEFAULT_GRID_SPACING;
                fallbackGridOffsetX = firstGridSceneConfig.offsetX ?? DEFAULT_GRID_X_OFFSET;
                fallbackGridOffsetY = firstGridSceneConfig.offsetY ?? DEFAULT_GRID_Y_OFFSET;
                fallbackGridSplitScreenTwoCameras = firstGridSceneConfig.splitScreenTwoCameras ?? DEFAULT_GRID_SPLIT_SCREEN;
            } else {
                logMessage(`No 'Grid' layout scene found. Using default grid settings as fallback for '${sceneName}'.`, "logMessages.obs.usingDefaultGridFallbackSettings", {targetScene: sceneName});
            }

            const fallbackGridConfig = {
                margin: fallbackGridMargin,
                spacing: fallbackGridSpacing,
                offsetX: fallbackGridOffsetX,
                offsetY: fallbackGridOffsetY,
                splitScreenTwoCameras: fallbackGridSplitScreenTwoCameras,
            };
            
            const cameraItemsForFallbackGrid = vdoNinjaSceneItems.filter(item =>
                item.sourceName.startsWith(generalCamPrefix) && !item.sourceName.endsWith(':s')
            );

            if (cameraItemsForFallbackGrid.length > 0) {
                 logMessage(`Applying fallback Grid layout to ${cameraItemsForFallbackGrid.length} camera sources in scene '${sceneName}'.`, "logMessages.obs.applyingFallbackGridToCameras", { count: cameraItemsForFallbackGrid.length, sceneName });
                 await applyGridLayoutToScene(sceneName, cameraItemsForFallbackGrid, canvasWidth, canvasHeight, fallbackGridConfig);
            } else {
                 logMessage(`No camera items to display in fallback grid for scene '${sceneName}'. All VDO sources in this scene will be hidden.`, "logMessages.obs.noCamerasForFallbackGridHidingAll", {sceneName});
            }

            const cameraItemIdsForFallback = new Set(cameraItemsForFallbackGrid.map(it => it.sceneItemId));
            for (const item of vdoNinjaSceneItems) {
                if (!cameraItemIdsForFallback.has(item.sceneItemId)) {
                    await hideSourceItem(sceneName, item.sceneItemId, item.sourceName);
                }
            }
            return; 
        }

        const mainSourceItem = mainContentItem; 
        const spacing = layoutConfig.spacing ?? DEFAULT_REACTION_HIGHLIGHT_SPACING;
        const offsetX = layoutConfig.offsetX ?? DEFAULT_REACTION_HIGHLIGHT_X_OFFSET;
        const offsetY = layoutConfig.offsetY ?? DEFAULT_REACTION_HIGHLIGHT_Y_OFFSET;
        const distributeCameras = layoutConfig.distributeCameras ?? DEFAULT_REACTION_HIGHLIGHT_DISTRIBUTE_CAMERAS;
        const cameraCropRatio = isReactionLayout ? REACTION_CAMERA_CROP_RATIO : HIGHLIGHT_CAMERA_CROP_RATIO;
        const itemAspectRatio = REACTION_HIGHLIGHT_ASPECT_RATIO; 

        let camsLeft = [], camsRight = [];
        if (distributeCameras && sideCameraSources.length > 0) {
            const half = Math.ceil(sideCameraSources.length / 2);
            camsLeft = sideCameraSources.slice(0, half);
            camsRight = sideCameraSources.slice(half);
        } else {
            camsLeft = [...sideCameraSources]; 
        }

        
        let leftCameraAreaWidth = camsLeft.length > 0 ? canvasWidth * 0.15 : 0;
        let rightCameraAreaWidth = camsRight.length > 0 ? canvasWidth * 0.15 : 0;

        
        let baseScaleWidthForMainContent = canvasWidth;
        baseScaleWidthForMainContent -= spacing;
        baseScaleWidthForMainContent -= spacing;
        if (leftCameraAreaWidth > 0) {
            baseScaleWidthForMainContent -= leftCameraAreaWidth;
            baseScaleWidthForMainContent -= spacing; 
        }
        if (rightCameraAreaWidth > 0) {
            baseScaleWidthForMainContent -= rightCameraAreaWidth;
            baseScaleWidthForMainContent -= spacing; 
        }
        
        let mainContentVisualWidth = Math.max(1, baseScaleWidthForMainContent);
        let mainContentVisualHeight = mainContentVisualWidth / itemAspectRatio;

        
        const availableHeightForScaling = canvasHeight - (2 * spacing);
        if (mainContentVisualHeight > availableHeightForScaling) {
            mainContentVisualHeight = Math.max(1, availableHeightForScaling);
            mainContentVisualWidth = mainContentVisualHeight * itemAspectRatio;
        }
        
        
        const mainContentVisualX = spacing + 
                                   (leftCameraAreaWidth > 0 ? (leftCameraAreaWidth + spacing) : 0) + 
                                   offsetX;
        
        const mainContentVisualY = (canvasHeight - mainContentVisualHeight) / 2 + offsetY;


        if (mainSourceItem) {
            const mainSrcInfo = await getInputSettingsSafe(mainSourceItem.sourceName, canvasWidth, canvasHeight);
            const transformMain = {
                alignment: 5, boundsType: "OBS_BOUNDS_NONE", 
                positionX: mainContentVisualX, 
                positionY: mainContentVisualY,
                scaleX: mainSrcInfo.width === 0 ? 0 : mainContentVisualWidth / mainSrcInfo.width,
                scaleY: mainSrcInfo.height === 0 ? 0 : mainContentVisualHeight / mainSrcInfo.height,
                width: mainContentVisualWidth, 
                height: mainContentVisualHeight, 
                sourceWidth: mainSrcInfo.width, 
                sourceHeight: mainSrcInfo.height,
                cropLeft: 0, cropRight: 0, cropTop: 0, cropBottom: 0,
            };
            await sendRequest("SetSceneItemEnabled", {sceneName, sceneItemId: mainSourceItem.sceneItemId, sceneItemEnabled: true});
            await sendRequest("SetSceneItemTransform", { sceneName, sceneItemId: mainSourceItem.sceneItemId, sceneItemTransform: transformMain });
        }

        const positionCameras = async (cameraList, startXForColumn, columnTotalWidth) => {
            if (cameraList.length > 0 && columnTotalWidth > 0) {
                const camAvailableHeightTotal = canvasHeight - (cameraList.length + 1) * spacing; 
                let camCellHeight = (cameraList.length > 0) ? Math.max(1, camAvailableHeightTotal / cameraList.length) : 0;
                
                // Calculate width based on height and aspect ratio, then apply crop
                let camCellOriginalWidthUncropped = camCellHeight * itemAspectRatio; // Camera's original aspect ratio assumed same as main for now
                let camCellVisibleWidthAfterCrop = camCellOriginalWidthUncropped * cameraCropRatio;

                // If visible width after crop is too wide for the column, or invalid, adjust.
                // Prioritize fitting into columnTotalWidth.
                if (camCellVisibleWidthAfterCrop > columnTotalWidth || camCellVisibleWidthAfterCrop <= 0) {
                     camCellVisibleWidthAfterCrop = Math.max(1, columnTotalWidth); 
                     if (cameraCropRatio > 0) { // Recalculate original width and height based on fitting visible width
                        camCellOriginalWidthUncropped = camCellVisibleWidthAfterCrop / cameraCropRatio;
                        camCellHeight = camCellOriginalWidthUncropped / itemAspectRatio;
                     } else { // No crop, so original width is visible width
                        camCellOriginalWidthUncropped = camCellVisibleWidthAfterCrop;
                        camCellHeight = camCellOriginalWidthUncropped / itemAspectRatio;
                     }
                }
                
                const actualCamerasBlockHeight = cameraList.length * camCellHeight + Math.max(0, cameraList.length - 1) * spacing;
                let currentCamY = (canvasHeight - actualCamerasBlockHeight) / 2 + offsetY; 

                for (const camItem of cameraList) {
                    const camSrcInfo = await getInputSettingsSafe(camItem.sourceName, 1920, 1080); 
                    
                    let cropLeftPixels = 0;
                    let cropRightPixels = 0;
                    let effectiveCamWidthForScale = camSrcInfo.width;

                    if (cameraCropRatio < 1.0 && cameraCropRatio > 0) { // Only apply crop if ratio is valid and less than 1
                        const totalHorizontalCrop = camSrcInfo.width * (1 - cameraCropRatio);
                        cropLeftPixels = Math.round(totalHorizontalCrop / 2);
                        cropRightPixels = Math.round(totalHorizontalCrop / 2);
                        effectiveCamWidthForScale = camSrcInfo.width - cropLeftPixels - cropRightPixels;
                    }


                    const transformCam = {
                        alignment: 5, boundsType: "OBS_BOUNDS_NONE",
                        positionX: startXForColumn + offsetX, // Apply offsetX to camera columns too
                        positionY: currentCamY, // offsetY is already in currentCamY
                        scaleX: effectiveCamWidthForScale <= 0 ? 0 : camCellVisibleWidthAfterCrop / effectiveCamWidthForScale,
                        scaleY: camSrcInfo.height <= 0 ? 0 : camCellHeight / camSrcInfo.height, 
                        width: camCellVisibleWidthAfterCrop,  // Visual width on canvas
                        height: camCellHeight, // Visual height on canvas
                        sourceWidth: camSrcInfo.width, 
                        sourceHeight: camSrcInfo.height,
                        cropLeft: cropLeftPixels, 
                        cropRight: cropRightPixels,
                        cropTop: 0, cropBottom: 0,
                    };
                    await sendRequest("SetSceneItemEnabled", {sceneName, sceneItemId: camItem.sceneItemId, sceneItemEnabled: true});
                    await sendRequest("SetSceneItemTransform", { sceneName, sceneItemId: camItem.sceneItemId, sceneItemTransform: transformCam });
                    currentCamY += camCellHeight + spacing;
                }
            }
        };
        
        // Position left cameras
        const startXLeftColumn = spacing; // Relative to canvas edge before offsetX
        await positionCameras(camsLeft, startXLeftColumn, leftCameraAreaWidth);

        // Position right cameras (if any)
        if (distributeCameras && camsRight.length > 0) {
            // Start X for right column is after main content and its preceding space
            const startXRightColumn = spacing + /*left edge*/
                                      (leftCameraAreaWidth > 0 ? (leftCameraAreaWidth + spacing) : 0) + /*left col area*/
                                      mainContentVisualWidth + spacing; /*main content and space after it*/
            await positionCameras(camsRight, startXRightColumn, rightCameraAreaWidth);
        }


        const layoutItemIds = new Set();
        if(mainSourceItem) layoutItemIds.add(mainSourceItem.sceneItemId);
        sideCameraSources.forEach(cam => layoutItemIds.add(cam.sceneItemId));

        for (const item of vdoNinjaSceneItems) {
            if (!layoutItemIds.has(item.sceneItemId)) {
                await hideSourceItem(sceneName, item.sceneItemId, item.sourceName);
            }
        }
      }

      // Apply reaction layout to scene
      async function applyReactionLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig) {
        await applyReactionOrHighlightLayoutShared(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig, true);
      }

      // Apply highlight layout to scene
      async function applyHighlightLayoutToScene(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig) {
        await applyReactionOrHighlightLayoutShared(sceneName, vdoNinjaSceneItems, canvasWidth, canvasHeight, layoutConfig, false);
      }

      // Calculate grid positions
      function calculateGridPositions( 
        totalSources, canvasWidth, canvasHeight, margin, spacing, cellAspectRatio, xOffset, yOffset
      ) {
        const positions = [];
        if (totalSources === 0) return positions;
        let cols = Math.ceil(Math.sqrt(totalSources));
        let rows = Math.ceil(totalSources / cols);
        if (cols === 0) cols = 1; 
        if (rows === 0) rows = 1; 
        const availableWidthAfterMargin = canvasWidth - 2 * margin;
        const availableHeightAfterMargin = canvasHeight - 2 * margin;
        if (availableWidthAfterMargin <= 0 || availableHeightAfterMargin <= 0) return positions; 
        let cellWidthInitial = (availableWidthAfterMargin - (cols - 1) * spacing) / cols;
        let cellHeightInitial = (availableHeightAfterMargin - (rows - 1) * spacing) / rows;
        if (cellWidthInitial <= 0 || cellHeightInitial <= 0) return positions;
        let finalCellWidth = cellWidthInitial;
        let finalCellHeight = cellHeightInitial;
        if (finalCellWidth / cellAspectRatio <= finalCellHeight) {
            finalCellHeight = finalCellWidth / cellAspectRatio;
        } else {
            finalCellWidth = finalCellHeight * cellAspectRatio;
        }
        if (finalCellWidth <= 0 || finalCellHeight <= 0) return positions;
        const totalBlockWidth = cols * finalCellWidth + Math.max(0, cols - 1) * spacing;
        const totalBlockHeight = rows * finalCellHeight + Math.max(0, rows - 1) * spacing;
        const verticalPaddingForBlock = (availableHeightAfterMargin - totalBlockHeight) / 2;
        for (let i = 0; i < totalSources; i++) {
            const rowIdx = Math.floor(i / cols);
            const colIdx = i % cols;
            const itemsInCurrentRow = (rowIdx === rows - 1) ? (totalSources - (rowIdx * cols)) : cols;
            const widthOfCurrentRow = itemsInCurrentRow * finalCellWidth + Math.max(0, itemsInCurrentRow - 1) * spacing;
            const horizontalPaddingForRow = (availableWidthAfterMargin - widthOfCurrentRow) / 2;
            const currentX = margin + horizontalPaddingForRow + colIdx * (finalCellWidth + spacing) + xOffset;
            const currentY = margin + verticalPaddingForBlock + rowIdx * (finalCellHeight + spacing) + yOffset;
            positions.push({
                x: currentX,
                y: currentY,
                width: finalCellWidth,
                height: finalCellHeight,
            });
        }
        return positions;
      }

      // Calculate transform for a scene item
      function calculateTransform(
        sizingMode, sourceWidth, sourceHeight, canvasWidth, canvasHeight, gridPosition = null 
      ) {
        let transform = {
          alignment: 5, boundsType: "OBS_BOUNDS_NONE", boundsAlignment: 0, 
          boundsWidth: sourceWidth, boundsHeight: sourceHeight, 
          positionX: (canvasWidth - sourceWidth) / 2, positionY: (canvasHeight - sourceHeight) / 2, 
          scaleX: 1.0, scaleY: 1.0, rotation: 0.0,
          cropTop: 0, cropBottom: 0, cropLeft: 0, cropRight: 0,
          sourceWidth: sourceWidth, sourceHeight: sourceHeight, 
          width: sourceWidth, height: sourceHeight, 
        };
        switch (sizingMode) {
          case "stretchToFill":
            transform.boundsType = "OBS_BOUNDS_STRETCH";
            transform.boundsWidth = canvasWidth; transform.boundsHeight = canvasHeight;
            transform.width = canvasWidth; transform.height = canvasHeight;
            transform.positionX = 0; transform.positionY = 0;
            break;
          case "bestFit": 
            transform.boundsType = "OBS_BOUNDS_SCALE_INNER"; 
            transform.boundsWidth = canvasWidth; transform.boundsHeight = canvasHeight;
            transform.width = canvasWidth; transform.height = canvasHeight;
            transform.positionX = 0; transform.positionY = 0;
            break;
          case "autoGrid": 
            if (gridPosition && gridPosition.width > 0 && gridPosition.height > 0) {
              transform.boundsType = "OBS_BOUNDS_NONE"; 
              transform.positionX = gridPosition.x;
              transform.positionY = gridPosition.y;
              transform.scaleX = (transform.sourceWidth === 0) ? 0 : gridPosition.width / transform.sourceWidth;
              transform.scaleY = (transform.sourceHeight === 0) ? 0 : gridPosition.height / transform.sourceHeight;
              transform.width = transform.sourceWidth * transform.scaleX;
              transform.height = transform.sourceHeight * transform.scaleY;
              transform.cropLeft = 0; transform.cropRight = 0; transform.cropTop = 0; transform.cropBottom = 0;
              transform.boundsWidth = gridPosition.width; transform.boundsHeight = gridPosition.height;
            } else { 
              transform.boundsType = "OBS_BOUNDS_SCALE_INNER";
              transform.boundsWidth = canvasWidth; transform.boundsHeight = canvasHeight;
              transform.width = canvasWidth; transform.height = canvasHeight;
              transform.positionX = 0; transform.positionY = 0;
            }
            break;
          case "defaultSize": 
          default:
            transform.positionX = 0; transform.positionY = 0;
            if (transform.sourceWidth > canvasWidth || transform.sourceHeight > canvasHeight) {
                const scaleRatioX = canvasWidth / transform.sourceWidth;
                const scaleRatioY = canvasHeight / transform.sourceHeight;
                const scale = Math.min(scaleRatioX, scaleRatioY);
                transform.scaleX = scale; transform.scaleY = scale;
                transform.width = transform.sourceWidth * scale; transform.height = transform.sourceHeight * scale;
                transform.positionX = (canvasWidth - transform.width) / 2; 
                transform.positionY = (canvasHeight - transform.height) / 2;
            } else { 
                transform.scaleX = 1.0; transform.scaleY = 1.0;
                transform.width = transform.sourceWidth; transform.height = transform.sourceHeight;
            }
            break;
        }
        return transform;
      }

      // Remove stream from OBS
      async function removeStreamFromObs(streamId) {
        if (!obsConnected || !obs) {
          logMessage(
            `Cannot remove stream ${streamId} from OBS: Not connected to OBS.`,
            "logMessages.obs.cannotRemoveStreamNotConnected", { id: streamId }
          );
          return;
        }
        const baseSourceName = `${getFullCameraPrefix()}_${streamId}`;
        const highlightedSourceName = `${getFullHighlightPrefix()}_${streamId}`; 
        logMessage(
          `User or auto-triggered removal of stream '${streamId}' from OBS. Base source: '${baseSourceName}'.`,
          "logMessages.obs.triggeredRemoval", { id: streamId, baseName: baseSourceName }
        );
        const streamInfo = activeStreams[streamId];
        const currentLabel = streamInfo ? streamInfo.label : (window.translation ? getTranslation("vdoNinja.defaultStreamLabel", {id: streamId}) : `Stream ${streamId}`);
        const targetInfo = getTargetSceneForStream(streamId, currentLabel);
        if (highlightedStreamId === streamId) {
          logMessage(`Stream ${streamId} was legacy highlighted. Unhighlighting.`, "logMessages.obs.legacyHighlightUnhighlightOnRemove", {id: streamId});
          await toggleHighlight(streamId, currentLabel, targetInfo); 
        }

        const scenesToClean = new Set(); 
        let allObsScenes = [];

        try {
            logMessage(`Fetching all scenes from OBS to ensure complete removal of ${streamId}...`, "logMessages.obs.fetchingAllScenesForRemoval", { id: streamId });
            const response = await sendRequest("GetSceneList");
            if (response && response.scenes) {
                allObsScenes = response.scenes;
                logMessage(`Found ${allObsScenes.length} total scenes for cleanup check.`, "logMessages.obs.foundScenesForCleanup", { count: allObsScenes.length });
            } else {
                logMessage("Could not fetch full scene list for removal, cleanup might be incomplete.", "logMessages.obs.fetchSceneListForRemovalFailed");
            }
        } catch (error) {
            logMessage(`Error fetching scene list for removal: ${error.message}. Cleanup might be incomplete.`, "logMessages.obs.errorFetchingSceneListForRemoval", { message: error.message });
        }

        for (const scene of allObsScenes) {
          const sceneName = scene.sceneName;
          if (!sceneName) continue; 
          
          let itemRemovedFromThisScene = false;
          
          if (await tryRemoveFromScene(baseSourceName, sceneName)) { 
            itemRemovedFromThisScene = true;
          }
          
          if (highlightedSourceName !== baseSourceName) {
             if (await tryRemoveFromScene(highlightedSourceName, sceneName)) {
                itemRemovedFromThisScene = true;
             }
          }
          
          if (itemRemovedFromThisScene) {
            scenesToClean.add(sceneName); 
          }
        }

        if (screenShareId === streamId) {
          logMessage(`Stream ${streamId} was actively screen sharing. Initiating screen share removal.`, "logMessages.obs.streamScreenSharingRemoving", { id: streamId });
          await removeScreenShareFromObs(streamId); 
        }
        if (activeStreams[streamId]) activeStreams[streamId].sourceCreated = false;
        logMessage(`Finished removing source items for stream ${streamId} from specified OBS scenes.`, "logMessages.obs.finishedRemovingSourceItems", { id: streamId });
        scenesToClean.forEach(scene => {
            if (scene) triggerLayoutUpdateForScene(scene);
        });
        if (screenShareId !== streamId) updateStreamList(); 
        saveSettings(); 
      }

      // Remove screen share from OBS
      async function removeScreenShareFromObs(streamIdToRemove) {
        if (!obsConnected || !obs) {
          logMessage("Cannot remove screen share: Not connected to OBS.", "logMessages.obs.cannotRemoveScreenShareNotConnected");
          return;
        }
        if (!streamIdToRemove) {
          if (screenShareId === streamIdToRemove) { 
            screenShareId = null;
            updateStreamList();
          }
          return;
        }
        const sourceName = `${getFullReactionPrefix()}_${streamIdToRemove}:s`; 
        logMessage(
          `Attempting to fully remove screen share source '${sourceName}' from OBS (all scenes and input).`,
          "logMessages.obs.attemptingRemoveScreenShareSource", { sourceName }
        );
        let itemRemovedFromAnyScene = false;
        const scenesWithItem = new Set();
        try {
          const reactionLayoutScenes = sceneLayoutConfigs
            .filter(cfg => cfg.layoutType === 'reaction' && cfg.sceneName)
            .map(cfg => cfg.sceneName);

          for (const sceneName of reactionLayoutScenes) {
            if (await tryRemoveFromScene(sourceName, sceneName)) {
                itemRemovedFromAnyScene = true;
                scenesWithItem.add(sceneName);
            }
          }
          
          const scenesResponse = await sendRequest("GetSceneList");
          if (scenesResponse && scenesResponse.scenes) {
            for (const scene of scenesResponse.scenes) {
              if (!scenesWithItem.has(scene.sceneName)) { 
                  if (await tryRemoveFromScene(sourceName, scene.sceneName)) {
                    itemRemovedFromAnyScene = true;
                    scenesWithItem.add(scene.sceneName);
                  }
              }
            }
          }

          try {
            await sendRequest("GetInputSettings", { inputName: sourceName }, { suppressNotFound: true });
            logMessage(`Removing global input '${sourceName}' from OBS.`, "logMessages.obs.removingGlobalInput", { sourceName });
            await sendRequest("RemoveInput", { inputName: sourceName });
            logMessage(`Successfully removed input '${sourceName}'.`, "logMessages.obs.successfullyRemovedInput", { sourceName });
          } catch (e) {  /* Suppress if input not found, already removed */ }
        } catch (error) {
          logMessage(
            `Error during screen share removal process for '${sourceName}': ${error.message}`,
            "logMessages.obs.errorScreenShareRemovalProcess", { sourceName, message: error.message }
          );
        } finally {
          if (screenShareId === streamIdToRemove) screenShareId = null;
          updateStreamList(); 
          scenesWithItem.forEach(sceneName => triggerLayoutUpdateForScene(sceneName));
        }
      }

      // Try to remove source from scene
      async function tryRemoveFromScene(sourceName, sceneName) {
        if (!sceneName) return false;
        try {
          const itemInfo = await sendRequest("GetSceneItemId", { sceneName, sourceName }, { suppressNotFound: true });
          if (itemInfo && itemInfo.sceneItemId) {
            logMessage(`Removing source item '${sourceName}' (ID: ${itemInfo.sceneItemId}) from scene '${sceneName}'.`, "logMessages.obs.removingSourceItemFromScene", { sourceName, itemId: itemInfo.sceneItemId, sceneName });
            await sendRequest("RemoveSceneItem", { sceneName, sceneItemId: itemInfo.sceneItemId });
            return true; 
          }
          return false; 
        } catch (error) {
          const isNotFound = error.message.toLowerCase().includes("not found") || error.message.toLowerCase().includes("no scene items were found") || error.message.toLowerCase().includes("could not find") || (error.message.toLowerCase().includes("code") && error.message.toLowerCase().includes("600")); 
          if (!isNotFound) {
            logMessage(`Error trying to remove source item '${sourceName}' from scene '${sceneName}': ${error.message}`, "logMessages.obs.errorTryingRemoveSourceItem", { sourceName, sceneName, message: error.message });
          }
          return false; 
        }
      }

      // Load jsSHA library (fallback)
      function loadJsShaLibrary() {
        return new Promise((resolve, reject) => {
          if (typeof jsSHA !== "undefined") {
            resolve();
            return;
          }
          const script = document.createElement("script");
          script.src = "https://cdnjs.cloudflare.com/ajax/libs/jsSHA/3.3.0/sha256.js"; 
          script.onload = () => {
            logMessage("jsSHA library loaded successfully (fallback for Web Crypto).", "logMessages.jsShaLoaded");
            resolve();
          };
          script.onerror = () => {
            logMessage("Error: Failed to load jsSHA library. OBS authentication might fail if Web Crypto is also unavailable.", "logMessages.errorLoadingJsSha");
            reject(new Error("Failed to load jsSHA library"));
          };
          document.head.appendChild(script);
        });
      }

      // Setup blur for secure fields
      function setupSecureFieldsBlur() {
        const secureFields = document.querySelectorAll(
          "#vdoNinjaRoom, #vdoNinjaStreamIds"
        ); 
        secureFields.forEach((field) => {
          field.classList.add("blur-field"); 
          field.addEventListener("focus", () =>
            field.classList.remove("blur-field")
          );
          field.addEventListener("blur", () => {
            if (field.value === "") field.classList.add("blur-field");
          });
          if (field.value === "") {
            field.classList.add("blur-field");
          }
        });
      }
      
      loadSourceCreationScenesBtn.addEventListener("click", fetchObsScenes);


      [
        obsWsUrlInput, obsWsPasswordInput,
        vdoNinjaBaseUrlInput, vdoNinjaRoomInput, vdoNinjaPasswordInput, vdoNinjaStreamIdsInput,
        autoAddSourcesCheckbox, autoRemoveSourcesCheckbox,
        document.getElementById("screenShareWidth"), document.getElementById("screenShareHeight"),
        sourceSizingSelect, 
        sourceCodecSelect
      ].forEach((el) => {
        if (el) el.addEventListener("change", saveSettings); 
      });
      sourceSizingSelect.addEventListener("change", () => {
        saveSettings();
        const defaultScene = getTargetScene(); 
        if (defaultScene && !getLayoutConfigForScene(defaultScene)) {
            triggerLayoutUpdateForScene(defaultScene);
        }
        obsScenes.forEach(scene => { 
            if (!getLayoutConfigForScene(scene.sceneName)) {
                triggerLayoutUpdateForScene(scene.sceneName);
            }
        });
      });
      sourceCodecSelect.addEventListener("change", async () => {
        saveSettings();
        const newCodec = sourceCodecSelect.value;
        logMessage(
          `Codec changed to: ${newCodec || "none"}. Updating OBS sources...`,
          "logMessages.obs.codecChangedUpdatingSources", { codec: newCodec || "none" }
        );
        if (!obsConnected) {
          logMessage("OBS is not connected. Sources will not be updated with the new codec until reconnection and a new action.", "logMessages.obs.codecChangedNotConnected");
          return;
        }
        for (const streamId in activeStreams) {
          if (Object.hasOwnProperty.call(activeStreams, streamId) && activeStreams[streamId].sourceCreated) {
            let effectiveSourceName = `${getFullCameraPrefix()}_${streamId}`;
            if (highlightedStreamId === streamId && getFullHighlightPrefix() !== getFullCameraPrefix()) {
                 effectiveSourceName = `${getFullHighlightPrefix()}_${streamId}`;
            }
            try {
              const currentSettingsResponse = await sendRequest("GetInputSettings", { inputName: effectiveSourceName }, { suppressNotFound: true });
              if (currentSettingsResponse && currentSettingsResponse.inputSettings) {
                const newViewUrl = getVdoNinjaViewUrl(streamId); 
                const updatedSettings = {
                  ...currentSettingsResponse.inputSettings,
                  url: newViewUrl, 
                  css: customCssInput.value.trim(),
                };
                await sendRequest("SetInputSettings", { inputName: effectiveSourceName, inputSettings: updatedSettings });
                logMessage(`Source '${effectiveSourceName}' updated with URL: ${newViewUrl} and custom CSS.`, "logMessages.obs.sourceUpdatedWithUrlAndCss", { sourceName: effectiveSourceName, url: newViewUrl });
              }
            } catch (error) { /* Suppress if source not found or other error during update */ }
          }
        }
        if (screenShareId) {
          const screenShareSourceName = `${getFullReactionPrefix()}_${screenShareId}:s`;
          try {
            const currentScreenShareSettingsResponse = await sendRequest("GetInputSettings", { inputName: screenShareSourceName }, { suppressNotFound: true });
            if (currentScreenShareSettingsResponse && currentScreenShareSettingsResponse.inputSettings) {
              const room = vdoNinjaRoomInput.value.trim(); 
              const baseUrl = getVdoNinjaBaseUrl();
              let newScreenShareUrl = `${baseUrl}/?view=${encodeURIComponent(screenShareId)}:s&solo&room=${encodeURIComponent(room)}`;
              if (newCodec && newCodec !== "") newScreenShareUrl += `&codec=${encodeURIComponent(newCodec)}`; else newScreenShareUrl += `&codec=vp9`;
              if (vdoNinjaPasswordInput.value) newScreenShareUrl += `&password=${encodeURIComponent(vdoNinjaPasswordInput.value)}`;
              newScreenShareUrl += "&cleanoutput&transparent&proaudio";
              const updatedScreenShareSettings = { ...currentScreenShareSettingsResponse.inputSettings, url: newScreenShareUrl, css: customCssInput.value.trim() };
              await sendRequest("SetInputSettings", { inputName: screenShareSourceName, inputSettings: updatedScreenShareSettings });
              logMessage(`Screen share source '${screenShareSourceName}' updated with URL: ${newScreenShareUrl} and custom CSS.`, "logMessages.obs.screenShareSourceUpdatedWithUrlAndCss", { sourceName: screenShareSourceName, url: newScreenShareUrl });
            }
          } catch (error) { /* Suppress if source not found or other error */ }
        }
        logMessage("Codec/CSS update for OBS sources complete.", "logMessages.obs.codecCssUpdateComplete");
      });
      document.addEventListener("DOMContentLoaded", () => { 
        initializeApp(); 
        updateLayoutConfigsListEmptyState(); 
      });
    </script>
  </body>
</html>